Guest User

Untitled

a guest
Jan 9th, 2018
104
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 17.34 KB | None | 0 0
  1. using TS3AudioBot.Audio;
  2. using TS3AudioBot.Helper;
  3. using System;
  4. using System.Collections.Generic;
  5. using System.Diagnostics;
  6. using System.Globalization;
  7. using System.Linq;
  8. using System.Text.RegularExpressions;
  9. using TS3Client;
  10. using TS3Client.Full;
  11. using TS3Client.Messages;
  12.  
  13. using TS3AudioBot;
  14.  
  15. using CSCore.Ffmpeg;
  16. using CSCore;
  17.  
  18. namespace CosyClient
  19. {
  20. public sealed class CosyTeamspeakClient : TeamspeakControl, IPlayerConnection, ITargetManager
  21. {
  22. public readonly Ts3FullClient TsFullClient;
  23.  
  24. private const Codec SendCodec = Codec.OpusMusic;
  25. private readonly TimeSpan sendCheckInterval = TimeSpan.FromMilliseconds(5);
  26. private readonly TimeSpan audioBufferLength = TimeSpan.FromMilliseconds(20);
  27. private const uint StallCountInterval = 10;
  28. private const uint StallNoErrorCountMax = 5;
  29.  
  30. private static readonly string[] QuitMessages = {
  31. "I'm outta here", "You're boring", "Have a nice day", "Bye", "Good night",
  32. "Nothing to do here", "Taking a break", "Lorem ipsum dolor sit amet…",
  33. "Nothing can hold me back", "It's getting quiet", "Drop the bazzzzzz",
  34. "Never gonna give you up", "Never gonna let you down", "Keep rockin' it",
  35. "?", "c(ꙩ_Ꙩ)ꜿ", "I'll be back", "Your advertisement could be here",
  36. "connection lost", "disconnected", "Requested by API.",
  37. "Robert'); DROP TABLE students;--", "It works!! No, wait...",
  38. "Notice me, senpai", ":wq"
  39. };
  40.  
  41. private const string PreLinkConf = "-hide_banner -nostats -i \"";
  42. private const string PostLinkConf = "\" -ac 2 -ar 48000 -f s16le -acodec pcm_s16le pipe:1";
  43. private string lastLink;
  44. private static readonly Regex FindDurationMatch = new Regex(@"^\s*Duration: (\d+):(\d\d):(\d\d).(\d\d)", Util.DefaultRegexConfig);
  45. private TimeSpan? parsedSongLength;
  46. private readonly object ffmpegLock = new object();
  47. private readonly TimeSpan retryOnDropBeforeEnd = TimeSpan.FromSeconds(10);
  48. private bool hasTriedToReconnectAudio;
  49.  
  50. private readonly Ts3FullClientData ts3FullClientData;
  51. private float volume = 1;
  52. public TargetSendMode SendMode { get; set; } = TargetSendMode.None;
  53. public ulong GroupWhisperTargetId { get; set; }
  54. public GroupWhisperType GroupWhisperType { get; set; }
  55. public GroupWhisperTarget GroupWhisperTarget { get; set; }
  56.  
  57. private TickWorker sendTick;
  58. private Process ffmpegProcess;
  59. private AudioEncoder encoder;
  60. private readonly PreciseAudioTimer audioTimer;
  61. private byte[] audioBuffer;
  62. private bool isStall;
  63. private uint stallCount;
  64. private uint stallNoErrorCount;
  65. private IdentityData identity;
  66.  
  67. private readonly Dictionary<ulong, bool> channelSubscriptionsSetup;
  68. private readonly List<ushort> clientSubscriptionsSetup;
  69. private ulong[] channelSubscriptionsCache;
  70. private ushort[] clientSubscriptionsCache;
  71. private bool subscriptionSetupChanged;
  72. private readonly object subscriptionLockObj = new object();
  73.  
  74. public CosyTeamspeakClient(Ts3FullClientData tfcd) : base(ClientType.Full)
  75. {
  76. TsFullClient = (Ts3FullClient)tsBaseClient;
  77.  
  78. ts3FullClientData = tfcd;
  79. tfcd.PropertyChanged += Tfcd_PropertyChanged;
  80.  
  81. sendTick = TickPool.RegisterTick(AudioSend, sendCheckInterval, false);
  82. encoder = new AudioEncoder(SendCodec) { Bitrate = ts3FullClientData.AudioBitrate * 1000 }; //48100
  83. audioTimer = new PreciseAudioTimer(encoder.SampleRate, encoder.BitsPerSample, encoder.Channels);
  84. isStall = false;
  85. stallCount = 0;
  86. identity = null;
  87.  
  88. Util.Init(ref channelSubscriptionsSetup);
  89. Util.Init(ref clientSubscriptionsSetup);
  90. subscriptionSetupChanged = true;
  91. }
  92.  
  93. public override T GetLowLibrary<T>()
  94. {
  95. if (typeof(T) == typeof(Ts3FullClient) && TsFullClient != null)
  96. return TsFullClient as T;
  97. return base.GetLowLibrary<T>();
  98. }
  99.  
  100. private void Tfcd_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
  101. {
  102. if (e.PropertyName == nameof(Ts3FullClientData.AudioBitrate))
  103. {
  104. var value = (int)typeof(Ts3FullClientData).GetProperty(e.PropertyName).GetValue(sender);
  105. if (value <= 0 || value >= 256)
  106. return;
  107. encoder.Bitrate = value * 1000;
  108. }
  109. }
  110.  
  111. public override void Connect()
  112. {
  113. // get or compute identity
  114. if (string.IsNullOrEmpty(ts3FullClientData.Identity))
  115. {
  116. identity = Ts3Crypt.GenerateNewIdentity();
  117. ts3FullClientData.Identity = identity.PrivateKeyString;
  118. ts3FullClientData.IdentityOffset = identity.ValidKeyOffset;
  119. }
  120. else
  121. {
  122. identity = Ts3Crypt.LoadIdentity(ts3FullClientData.Identity, ts3FullClientData.IdentityOffset);
  123. }
  124.  
  125. // check required security level
  126. if (ts3FullClientData.IdentityLevel == "auto") { }
  127. else if (int.TryParse(ts3FullClientData.IdentityLevel, out int targetLevel))
  128. {
  129. Log.Write(Log.Level.Info, "Calculating up to required security level: {0}", targetLevel);
  130. Ts3Crypt.ImproveSecurity(identity, targetLevel);
  131. ts3FullClientData.IdentityOffset = identity.ValidKeyOffset;
  132. }
  133. else
  134. {
  135. Log.Write(Log.Level.Warning, "Invalid value for QueryConnection::IdentityLevel, enter a number or \"auto\".");
  136. }
  137.  
  138. // get or compute password
  139. if (!string.IsNullOrEmpty(ts3FullClientData.ServerPassword)
  140. && ts3FullClientData.ServerPasswordAutoHash
  141. && !ts3FullClientData.ServerPasswordIsHashed)
  142. {
  143. ts3FullClientData.ServerPassword = Ts3Crypt.HashPassword(ts3FullClientData.ServerPassword);
  144. ts3FullClientData.ServerPasswordIsHashed = true;
  145. }
  146.  
  147. TsFullClient.QuitMessage = QuitMessages[Util.Random.Next(0, QuitMessages.Length)];
  148. TsFullClient.OnErrorEvent += TsFullClient_OnErrorEvent;
  149. ConnectClient();
  150. }
  151.  
  152. private void ConnectClient()
  153. {
  154. VersionSign verionSign;
  155. if (!string.IsNullOrEmpty(ts3FullClientData.ClientVersion))
  156. {
  157. var splitData = ts3FullClientData.ClientVersion.Split('|').Select(x => x.Trim()).ToArray();
  158. var plattform = (ClientPlattform)Enum.Parse(typeof(ClientPlattform), splitData[1], true);
  159. verionSign = new VersionSign(splitData[0], plattform, splitData[2]);
  160. }
  161. else if (Util.IsLinux)
  162. verionSign = VersionSign.VER_LIN_3_0_19_4;
  163. else
  164. verionSign = VersionSign.VER_WIN_3_0_19_4;
  165.  
  166. TsFullClient.Connect(new ConnectionDataFull
  167. {
  168. Username = ts3FullClientData.DefaultNickname,
  169. Password = ts3FullClientData.ServerPassword,
  170. Address = ts3FullClientData.Address,
  171. Identity = identity,
  172. IsPasswordHashed = ts3FullClientData.ServerPasswordIsHashed,
  173. VersionSign = verionSign,
  174. DefaultChannel = ts3FullClientData.DefaultChannel,
  175. });
  176. }
  177.  
  178. private void TsFullClient_OnErrorEvent(object sender, CommandError e)
  179. {
  180. switch (e.Id)
  181. {
  182. case Ts3ErrorCode.whisper_no_targets:
  183. stallNoErrorCount = 0;
  184. isStall = true;
  185. break;
  186.  
  187. case Ts3ErrorCode.client_could_not_validate_identity:
  188. if (ts3FullClientData.IdentityLevel == "auto")
  189. {
  190. int targetSecLevel = int.Parse(e.ExtraMessage);
  191. Log.Write(Log.Level.Info, "Calculating up to required security level: {0}", targetSecLevel);
  192. Ts3Crypt.ImproveSecurity(identity, targetSecLevel);
  193. ts3FullClientData.IdentityOffset = identity.ValidKeyOffset;
  194.  
  195. ConnectClient();
  196. }
  197. else
  198. {
  199. Log.Write(Log.Level.Warning, "The server reported that the security level you set is not high enough." +
  200. "Increase the value to \"{0}\" or set it to \"auto\" to generate it on demand when connecting.", e.ExtraMessage);
  201. }
  202. break;
  203.  
  204. default:
  205. Log.Write(Log.Level.Debug, e.ErrorFormat());
  206. break;
  207. }
  208. }
  209.  
  210. public override ClientData GetSelf()
  211. {
  212. var data = tsBaseClient.WhoAmI();
  213. var cd = new ClientData
  214. {
  215. Uid = identity.ClientUid,
  216. ChannelId = data.ChannelId,
  217. ClientId = TsFullClient.ClientId,
  218. NickName = data.NickName,
  219. ClientType = tsBaseClient.ClientType
  220. };
  221. try
  222. {
  223. var response = tsBaseClient.Send("clientgetdbidfromuid", new TS3Client.Commands.CommandParameter("cluid", identity.ClientUid)).FirstOrDefault();
  224. if (response != null && ulong.TryParse(response["cldbid"], out var dbId))
  225. cd.DatabaseId = dbId;
  226. }
  227. catch (Ts3CommandException) { }
  228. return cd;
  229. }
  230.  
  231. private void AudioSend()
  232. {
  233. lock (ffmpegLock)
  234. {
  235. if (_ffmpegDecoder == null)
  236. return;
  237.  
  238. if (audioBuffer == null || audioBuffer.Length < encoder.OptimalPacketSize)
  239. audioBuffer = new byte[encoder.OptimalPacketSize];
  240.  
  241. while (audioTimer.RemainingBufferDuration < audioBufferLength)
  242. {
  243. int read = _ffmpegDecoder.Read(audioBuffer, 0, encoder.OptimalPacketSize);
  244.  
  245. if (read == 0)
  246. {
  247. // check for premature connection drop
  248. if (ffmpegProcess.HasExited && !hasTriedToReconnectAudio)
  249. {
  250. var expectedStopLength = GetCurrentSongLength();
  251. if (expectedStopLength != TimeSpan.Zero)
  252. {
  253. var actualStopPosition = audioTimer.SongPosition;
  254. if (actualStopPosition + retryOnDropBeforeEnd < expectedStopLength)
  255. {
  256. Log.Write(Log.Level.Debug, "Connection to song lost, retrying at {0}", actualStopPosition);
  257. hasTriedToReconnectAudio = true;
  258. Position = actualStopPosition;
  259. return;
  260. }
  261. }
  262. }
  263.  
  264. if (ffmpegProcess.HasExited
  265. && audioTimer.RemainingBufferDuration < TimeSpan.Zero
  266. && !encoder.HasPacket)
  267. {
  268. AudioStop();
  269. OnSongEnd?.Invoke(this, new EventArgs());
  270. }
  271. return;
  272. }
  273.  
  274. hasTriedToReconnectAudio = false;
  275. audioTimer.PushBytes(read);
  276.  
  277. bool doSend = true;
  278.  
  279. switch (SendMode)
  280. {
  281. case TargetSendMode.None:
  282. doSend = false;
  283. break;
  284.  
  285. case TargetSendMode.Voice:
  286. break;
  287.  
  288. case TargetSendMode.Whisper:
  289. case TargetSendMode.WhisperGroup:
  290. if (isStall)
  291. {
  292. if (++stallCount % StallCountInterval == 0)
  293. {
  294. stallNoErrorCount++;
  295. if (stallNoErrorCount > StallNoErrorCountMax)
  296. {
  297. stallCount = 0;
  298. isStall = false;
  299. }
  300. }
  301. else
  302. {
  303. doSend = false;
  304. }
  305. }
  306. if (SendMode == TargetSendMode.Whisper)
  307. doSend &= channelSubscriptionsCache.Length > 0 || clientSubscriptionsCache.Length > 0;
  308. break;
  309.  
  310. default:
  311. throw new InvalidOperationException();
  312. }
  313.  
  314. // Save cpu when we know there is noone to send to
  315. if (!doSend)
  316. break;
  317.  
  318. AudioModifier.AdjustVolume(audioBuffer, read, volume);
  319. encoder.PushPcmAudio(audioBuffer, read);
  320. while (encoder.HasPacket)
  321. {
  322. var packet = encoder.GetPacket();
  323. switch (SendMode)
  324. {
  325. case TargetSendMode.Voice:
  326. TsFullClient.SendAudio(packet.Array, packet.Length, encoder.Codec);
  327. break;
  328.  
  329. case TargetSendMode.Whisper:
  330. TsFullClient.SendAudioWhisper(packet.Array, packet.Length, encoder.Codec, channelSubscriptionsCache, clientSubscriptionsCache);
  331. break;
  332.  
  333. case TargetSendMode.WhisperGroup:
  334. TsFullClient.SendAudioGroupWhisper(packet.Array, packet.Length, encoder.Codec, GroupWhisperType, GroupWhisperTarget);
  335. break;
  336. }
  337. encoder.ReturnPacket(packet.Array);
  338. }
  339. }
  340. }
  341. }
  342.  
  343. #region IPlayerConnection
  344.  
  345. public event EventHandler OnSongEnd;
  346.  
  347. public void SetGroupWhisper(GroupWhisperType type, GroupWhisperTarget target, ulong targetId = 0)
  348. {
  349. GroupWhisperType = type;
  350. GroupWhisperTarget = target;
  351. GroupWhisperTargetId = targetId;
  352. }
  353.  
  354. private IWaveSource _ffmpegDecoder;
  355.  
  356. public R AudioStart(string url)
  357. {
  358. //_ffmpegDecoder = new FfmpegDecoder(url).ChangeSampleRate(48100);//48100 //ts3FullClientData.AudioBitrate * 1000
  359.  
  360. var smp = new FfmpegDecoder(url).ToSampleSource();//.ChangeSampleRate(48100)
  361. _ffmpegDecoder = new CSCore.Streams.SampleConverter.SampleToPcm16(smp);
  362.  
  363. lastLink = url;
  364. parsedSongLength = null;
  365.  
  366. audioTimer.SongPositionOffset = TimeSpan.Zero;
  367. audioTimer.Start();
  368. sendTick.Active = true;
  369.  
  370. return R.OkR;
  371. }
  372.  
  373. //StartFfmpegProcess(url);
  374.  
  375. public R AudioStop()
  376. {
  377. sendTick.Active = false;
  378. audioTimer.Stop();
  379.  
  380. if (_ffmpegDecoder != null)
  381. {
  382. _ffmpegDecoder.Dispose();
  383. _ffmpegDecoder = null;
  384. }
  385.  
  386. return R.OkR;
  387. }
  388.  
  389. public TimeSpan Length => GetCurrentSongLength();
  390.  
  391. public TimeSpan Position
  392. {
  393. get => _ffmpegDecoder.GetPosition();
  394. set
  395. {
  396. if (value < TimeSpan.Zero || value > Length)
  397. throw new ArgumentOutOfRangeException(nameof(value));
  398.  
  399. _ffmpegDecoder.SetPosition(value);
  400. /*
  401. AudioStop();
  402. StartFfmpegProcess(lastLink,
  403. $"-ss {value.ToString(@"hh\:mm\:ss", CultureInfo.InvariantCulture)}",
  404. $"-ss {value.ToString(@"hh\:mm\:ss", CultureInfo.InvariantCulture)}");
  405. audioTimer.SongPositionOffset = value;
  406. */
  407. }
  408. }
  409.  
  410. public int Volume
  411. {
  412. get => (int)Math.Round(volume * AudioValues.MaxVolume);
  413. set
  414. {
  415. if (value < 0 || value > AudioValues.MaxVolume)
  416. throw new ArgumentOutOfRangeException(nameof(value));
  417. volume = value / (float)AudioValues.MaxVolume;
  418. }
  419. }
  420.  
  421. public bool Paused
  422. {
  423. get => sendTick.Active;
  424. set
  425. {
  426. if (sendTick.Active == value)
  427. {
  428. sendTick.Active = !value;
  429. if (value)
  430. {
  431. audioTimer.SongPositionOffset = audioTimer.SongPosition;
  432. audioTimer.Stop();
  433. }
  434. else
  435. audioTimer.Start();
  436. }
  437. }
  438. }
  439.  
  440. public bool Playing => sendTick.Active;
  441.  
  442. public bool Repeated { get { return false; } set { } }
  443.  
  444. private R StartFfmpegProcess(string url, string extraPreParam = null, string extraPostParam = null)
  445. {
  446. try
  447. {
  448. lock (ffmpegLock)
  449. {
  450. StopFfmpegProcess();
  451.  
  452. ffmpegProcess = new Process
  453. {
  454. StartInfo = new ProcessStartInfo
  455. {
  456. FileName = ts3FullClientData.FfmpegPath,
  457. Arguments = string.Concat(extraPreParam, " ", PreLinkConf, url, PostLinkConf, " ", extraPostParam),
  458. RedirectStandardOutput = true,
  459. RedirectStandardInput = true,
  460. RedirectStandardError = true,
  461. UseShellExecute = false,
  462. CreateNoWindow = true,
  463. }
  464. };
  465. ffmpegProcess.Start();
  466.  
  467. lastLink = url;
  468. parsedSongLength = null;
  469.  
  470. audioTimer.SongPositionOffset = TimeSpan.Zero;
  471. audioTimer.Start();
  472. sendTick.Active = true;
  473. return R.OkR;
  474. }
  475. }
  476. catch (Exception ex) { return $"Unable to create stream ({ex.Message})"; }
  477. }
  478.  
  479. private void StopFfmpegProcess()
  480. {
  481. lock (ffmpegLock)
  482. {
  483. if (ffmpegProcess == null)
  484. return;
  485.  
  486. try
  487. {
  488. if (!ffmpegProcess.HasExited)
  489. ffmpegProcess.Kill();
  490. else
  491. ffmpegProcess.Close();
  492. }
  493. catch (InvalidOperationException) { }
  494. ffmpegProcess = null;
  495. }
  496. }
  497.  
  498. private TimeSpan GetCurrentSongLength()
  499. {
  500. return _ffmpegDecoder?.GetLength() ?? TimeSpan.MaxValue;
  501. //lock (ffmpegLock)
  502. //{
  503. // if (ffmpegProcess == null)
  504. // return TimeSpan.Zero;
  505.  
  506. // if (parsedSongLength.HasValue)
  507. // return parsedSongLength.Value;
  508.  
  509. // Match match = null;
  510. // while (ffmpegProcess.StandardError.Peek() > -1)
  511. // {
  512. // var infoLine = ffmpegProcess.StandardError.ReadLine();
  513. // if (string.IsNullOrEmpty(infoLine))
  514. // continue;
  515. // match = FindDurationMatch.Match(infoLine);
  516. // if (match.Success)
  517. // break;
  518. // }
  519. // if (match == null || !match.Success)
  520. // return TimeSpan.Zero;
  521.  
  522. // int hours = int.Parse(match.Groups[1].Value, CultureInfo.InvariantCulture);
  523. // int minutes = int.Parse(match.Groups[2].Value, CultureInfo.InvariantCulture);
  524. // int seconds = int.Parse(match.Groups[3].Value, CultureInfo.InvariantCulture);
  525. // int millisec = int.Parse(match.Groups[4].Value, CultureInfo.InvariantCulture) * 10;
  526. // parsedSongLength = new TimeSpan(0, hours, minutes, seconds, millisec);
  527. // return parsedSongLength.Value;
  528. //}
  529. }
  530.  
  531. #endregion IPlayerConnection
  532.  
  533. #region ITargetManager
  534.  
  535. public void WhisperChannelSubscribe(ulong channel, bool temp)
  536. {
  537. // TODO move to requested channel
  538. // TODO spawn new client
  539. lock (subscriptionLockObj)
  540. {
  541. if (channelSubscriptionsSetup.TryGetValue(channel, out var subscriptionTemp))
  542. channelSubscriptionsSetup[channel] = !subscriptionTemp || !temp;
  543. else
  544. {
  545. channelSubscriptionsSetup[channel] = !temp;
  546. subscriptionSetupChanged = true;
  547. }
  548. }
  549. }
  550.  
  551. public void WhisperChannelUnsubscribe(ulong channel, bool temp)
  552. {
  553. lock (subscriptionLockObj)
  554. {
  555. if (!temp)
  556. {
  557. subscriptionSetupChanged |= channelSubscriptionsSetup.Remove(channel);
  558. }
  559. else
  560. {
  561. if (channelSubscriptionsSetup.TryGetValue(channel, out bool subscriptionTemp) && subscriptionTemp)
  562. {
  563. channelSubscriptionsSetup.Remove(channel);
  564. subscriptionSetupChanged = true;
  565. }
  566. }
  567. }
  568. }
  569.  
  570. public void WhisperClientSubscribe(ushort userId)
  571. {
  572. lock (subscriptionLockObj)
  573. {
  574. if (!clientSubscriptionsSetup.Contains(userId))
  575. clientSubscriptionsSetup.Add(userId);
  576. subscriptionSetupChanged = true;
  577. }
  578. }
  579.  
  580. public void WhisperClientUnsubscribe(ushort userId)
  581. {
  582. lock (subscriptionLockObj)
  583. {
  584. clientSubscriptionsSetup.Remove(userId);
  585. subscriptionSetupChanged = true;
  586. }
  587. }
  588.  
  589. public void ClearTemporary()
  590. {
  591. lock (subscriptionLockObj)
  592. {
  593. ulong[] removeList = channelSubscriptionsSetup
  594. .Where(kvp => kvp.Value)
  595. .Select(kvp => kvp.Key)
  596. .ToArray();
  597. foreach (var chan in removeList)
  598. {
  599. channelSubscriptionsSetup.Remove(chan);
  600. subscriptionSetupChanged = true;
  601. }
  602. }
  603. }
  604.  
  605. private void UpdatedSubscriptionCache()
  606. {
  607. if (!subscriptionSetupChanged)
  608. return;
  609. lock (subscriptionLockObj)
  610. {
  611. if (!subscriptionSetupChanged)
  612. return;
  613. channelSubscriptionsCache = channelSubscriptionsSetup.Keys.ToArray();
  614. clientSubscriptionsCache = clientSubscriptionsSetup.ToArray();
  615. subscriptionSetupChanged = false;
  616. }
  617. }
  618.  
  619. #endregion ITargetManager
  620. }
  621. }
Add Comment
Please, Sign In to add comment