1
0

PlayThread.cpp 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703
  1. #include "main.h"
  2. #include <windows.h>
  3. #include "api__in_flv.h"
  4. #include "../Winamp/wa_ipc.h"
  5. #include "FLVHeader.h"
  6. #include "FLVStreamHeader.h"
  7. #include "FLVAudioHeader.h"
  8. #include "FLVVideoHeader.h"
  9. #include "FLVMetadata.h"
  10. #include <malloc.h>
  11. #include <stdio.h>
  12. #include <shlwapi.h>
  13. #include "VideoThread.h"
  14. #include "FLVReader.h"
  15. #include "resource.h"
  16. #include "FLVCOM.h"
  17. #include <api/service/waservicefactory.h>
  18. #include "ifc_flvaudiodecoder.h"
  19. #include "svc_flvdecoder.h"
  20. #include "../nu/AudioOutput.h"
  21. #define PRE_BUFFER_MS 5000.0
  22. #define PRE_BUFFER_MAX (1024*1024)
  23. uint32_t last_timestamp=0;
  24. static bool audioOpened;
  25. int bufferCount;
  26. static int bits, channels, sampleRate;
  27. static double dataRate_audio, dataRate_video;
  28. static double dataRate;
  29. static uint64_t prebuffer;
  30. bool mute=false;
  31. uint32_t first_timestamp;
  32. static size_t audio_buffered=0;
  33. void VideoStop();
  34. static ifc_flvaudiodecoder *audioDecoder=0;
  35. static bool checked_in_swf=false;
  36. extern bool video_only;
  37. class FLVWait
  38. {
  39. public:
  40. int WaitOrAbort(int len)
  41. {
  42. if (WaitForSingleObject(killswitch, len) == WAIT_OBJECT_0)
  43. return 1;
  44. return 0;
  45. }
  46. };
  47. static nu::AudioOutput<FLVWait> outputter(&plugin);
  48. static void Buffering(int bufStatus, const wchar_t *displayString)
  49. {
  50. if (bufStatus < 0 || bufStatus > 100)
  51. return;
  52. char tempdata[75*2] = {0, };
  53. int csa = plugin.SAGetMode();
  54. if (csa & 1)
  55. {
  56. for (int x = 0; x < bufStatus*75 / 100; x ++)
  57. tempdata[x] = x * 16 / 75;
  58. }
  59. else if (csa&2)
  60. {
  61. int offs = (csa & 1) ? 75 : 0;
  62. int x = 0;
  63. while (x < bufStatus*75 / 100)
  64. {
  65. tempdata[offs + x++] = -6 + x * 14 / 75;
  66. }
  67. while (x < 75)
  68. {
  69. tempdata[offs + x++] = 0;
  70. }
  71. }
  72. else if (csa == 4)
  73. {
  74. tempdata[0] = tempdata[1] = (bufStatus * 127 / 100);
  75. }
  76. if (csa) plugin.SAAdd(tempdata, ++bufferCount, (csa == 3) ? 0x80000003 : csa);
  77. /*
  78. TODO
  79. wchar_t temp[64] = {0};
  80. StringCchPrintf(temp, 64, L"%s: %d%%",displayString, bufStatus);
  81. SetStatus(temp);
  82. */
  83. //SetVideoStatusText(temp); // TODO: find a way to set the old status back
  84. videoOutput->notifyBufferState(static_cast<int>(bufStatus*2.55f));
  85. }
  86. static bool Audio_IsSupported(int type)
  87. {
  88. size_t n = 0;
  89. waServiceFactory *factory = NULL;
  90. while (factory = plugin.service->service_enumService(WaSvc::FLVDECODER, n++))
  91. {
  92. svc_flvdecoder *creator = (svc_flvdecoder *)factory->getInterface();
  93. if (creator)
  94. {
  95. int supported = creator->HandlesAudio(type);
  96. factory->releaseInterface(creator);
  97. if (supported == svc_flvdecoder::CREATEDECODER_SUCCESS)
  98. return true;
  99. }
  100. }
  101. return false;
  102. }
  103. static ifc_flvaudiodecoder *CreateAudioDecoder(const FLVAudioHeader &header)
  104. {
  105. ifc_flvaudiodecoder *audio_decoder=0;
  106. size_t n=0;
  107. waServiceFactory *factory = NULL;
  108. while (factory = plugin.service->service_enumService(WaSvc::FLVDECODER, n++))
  109. {
  110. svc_flvdecoder *creator = (svc_flvdecoder *)factory->getInterface();
  111. if (creator)
  112. {
  113. if (creator->CreateAudioDecoder(header.stereo, header.bits, header.sampleRate, header.format, &audio_decoder) == FLV_AUDIO_SUCCESS)
  114. return audio_decoder;
  115. factory->releaseInterface(creator);
  116. }
  117. }
  118. return 0;
  119. }
  120. void OnStart()
  121. {
  122. Video_Init();
  123. audioOpened = false;
  124. audioDecoder = 0;
  125. bufferCount=0;
  126. mute = false;
  127. audio_buffered=0;
  128. if (!videoOutput)
  129. videoOutput = (IVideoOutput *)SendMessage(plugin.hMainWindow, WM_WA_IPC, 0, IPC_GET_IVIDEOOUTPUT);
  130. }
  131. void Audio_Close()
  132. {
  133. if (audioDecoder)
  134. audioDecoder->Close();
  135. audioDecoder=0;
  136. }
  137. static bool OpenAudio(unsigned int sampleRate, unsigned int channels, unsigned int bits)
  138. {
  139. if (!outputter.Open(0, channels, sampleRate, bits, -666))
  140. return false;
  141. audioOpened = true;
  142. return true;
  143. }
  144. static void DoBuffer(FLVReader &reader)
  145. {
  146. // TODO: we should pre-buffer after getting the first audio + video frames
  147. // so we can estimate bitrate
  148. for (;;)
  149. {
  150. uint64_t processedPosition = reader.GetProcessedPosition();
  151. if (processedPosition < prebuffer && !reader.IsEOF())
  152. {
  153. Buffering((int)((100ULL * processedPosition) / prebuffer), WASABI_API_LNGSTRINGW(IDS_BUFFERING));
  154. if (WaitForSingleObject(killswitch, 100) == WAIT_OBJECT_0)
  155. break;
  156. else
  157. continue;
  158. }
  159. else
  160. break;
  161. }
  162. }
  163. char pcmdata[65536] = {0};
  164. size_t outlen = 32768;
  165. static uint32_t audio_type;
  166. static void OnAudio(FLVReader &reader, void *data, size_t length, const FLVAudioHeader &header)
  167. {
  168. if (!audioDecoder)
  169. {
  170. audioDecoder = CreateAudioDecoder(header);
  171. audio_type = header.format;
  172. video_only=false;
  173. }
  174. if (audioDecoder)
  175. {
  176. if (!audioDecoder->Ready())
  177. {
  178. //first_timestamp = -1;
  179. }
  180. outlen = sizeof(pcmdata)/2 - audio_buffered;
  181. double bitrate = 0;
  182. int ret = audioDecoder->DecodeSample(data, length, pcmdata+audio_buffered, &outlen, &bitrate);
  183. if (ret == FLV_AUDIO_SUCCESS)
  184. {
  185. outlen+=audio_buffered;
  186. audio_buffered=0;
  187. if (bitrate && dataRate_audio != bitrate)
  188. {
  189. dataRate_audio = bitrate;
  190. dataRate = dataRate_audio + dataRate_video;
  191. plugin.SetInfo((int)dataRate, -1, -1, 1);
  192. }
  193. if (!audioOpened)
  194. {
  195. // pre-populate values for decoders that use the header info (e.g. ADPCM)
  196. sampleRate=header.sampleRate;
  197. channels = header.stereo?2:1;
  198. bits = header.bits;
  199. if (audioDecoder->GetOutputFormat((unsigned int *)&sampleRate, (unsigned int *)&channels, (unsigned int *)&bits) == FLV_AUDIO_SUCCESS)
  200. {
  201. // buffer (if needed)
  202. prebuffer = (uint64_t)(dataRate * PRE_BUFFER_MS / 8.0);
  203. if (prebuffer > PRE_BUFFER_MAX)
  204. prebuffer=PRE_BUFFER_MAX;
  205. DoBuffer(reader); // benski> admittedly a crappy place to call this
  206. if (WaitForSingleObject(killswitch, 0) == WAIT_OBJECT_0)
  207. return;
  208. OpenAudio(sampleRate, channels, bits);
  209. }
  210. }
  211. if (mute)
  212. {
  213. if (bits == 8) // 8 bit is signed so 128 is zero voltage
  214. memset(pcmdata, 0x80, outlen);
  215. else
  216. memset(pcmdata, 0, outlen);
  217. }
  218. if (audioOpened && outlen)
  219. {
  220. outputter.Write(pcmdata, outlen);
  221. }
  222. else
  223. {
  224. audio_buffered=outlen;
  225. }
  226. }
  227. else if (ret == FLV_AUDIO_NEEDS_MORE_INPUT)
  228. {
  229. plugin.SetInfo(-1, -1, -1, 0);
  230. }
  231. }
  232. }
  233. #define PREBUFFER_BYTES 2048ULL
  234. enum
  235. {
  236. CODEC_CHECK_NONE=0,
  237. CODEC_CHECK_AUDIO=1,
  238. CODEC_CHECK_VIDEO=2,
  239. CODEC_CHECK_UNSURE = -1,
  240. };
  241. static bool CheckSWF()
  242. {
  243. if (!checked_in_swf)
  244. {
  245. const wchar_t *pluginsDir = (const wchar_t *)SendMessage(plugin.hMainWindow, WM_WA_IPC, 0, IPC_GETPLUGINDIRECTORYW);
  246. wchar_t in_swf_path[MAX_PATH] = {0};
  247. PathCombineW(in_swf_path, pluginsDir, L"in_swf.dll");
  248. in_swf = LoadLibraryW(in_swf_path);
  249. checked_in_swf = true;
  250. }
  251. return !!in_swf;
  252. }
  253. static void CALLBACK SWFAPC(ULONG_PTR param)
  254. {
  255. if (in_swf)
  256. {
  257. typedef In_Module *(*MODULEGETTER)();
  258. MODULEGETTER moduleGetter=0;
  259. moduleGetter = (MODULEGETTER)GetProcAddress(in_swf, "winampGetInModule2");
  260. if (moduleGetter)
  261. swf_mod = moduleGetter();
  262. }
  263. if (swf_mod)
  264. {
  265. if (swf_mod->Play(playFile))
  266. swf_mod=0;
  267. }
  268. if (!swf_mod)
  269. {
  270. if (WaitForSingleObject(killswitch, 0) != WAIT_OBJECT_0)
  271. PostMessage(plugin.hMainWindow, WM_WA_MPEG_EOF, 0, 0);
  272. }
  273. }
  274. static bool Audio_DecoderReady()
  275. {
  276. if (!audioDecoder)
  277. return true;
  278. return !!audioDecoder->Ready();
  279. }
  280. static bool DecodersReady()
  281. {
  282. return Audio_DecoderReady() && Video_DecoderReady();
  283. }
  284. DWORD CALLBACK PlayProcedure(LPVOID param)
  285. {
  286. int needCodecCheck=CODEC_CHECK_UNSURE;
  287. int missingCodecs=CODEC_CHECK_NONE;
  288. size_t codecCheckFrame=0;
  289. dataRate_audio=0;
  290. dataRate_video=0;
  291. dataRate=0;
  292. first_timestamp=-1;
  293. outputter.Init(plugin.outMod);
  294. FLVReader reader(playFile);
  295. size_t i=0;
  296. bool hasDuration=false;
  297. OnStart();
  298. plugin.is_seekable=0;
  299. prebuffer = PREBUFFER_BYTES;
  300. bool first_frame_parsed=false;
  301. for (;;)
  302. {
  303. DoBuffer(reader);
  304. if (WaitForSingleObject(killswitch, paused?100:0) == WAIT_OBJECT_0)
  305. break;
  306. if (paused)
  307. continue;
  308. if (m_need_seek != -1 && DecodersReady() && first_frame_parsed)
  309. {
  310. if (reader.GetPosition(m_need_seek, &i, video_opened))
  311. {
  312. VideoFlush();
  313. if (audioDecoder)
  314. audioDecoder->Flush();
  315. FrameData frameData;
  316. reader.GetFrame(i, frameData);
  317. outputter.Flush(frameData.header.timestamp);
  318. if (video_only)
  319. {
  320. video_clock.Seek(frameData.header.timestamp);
  321. }
  322. }
  323. uint32_t first_timestamp = 0;
  324. m_need_seek=-1;
  325. }
  326. // update the movie length
  327. if (!hasDuration)
  328. {
  329. if (reader.IsStreaming())
  330. {
  331. hasDuration=true;
  332. g_length = -1000;
  333. plugin.is_seekable=0;
  334. }
  335. else
  336. {
  337. g_length=reader.GetMaxTimestamp();
  338. if (g_length != -1000)
  339. plugin.is_seekable=1;
  340. }
  341. PostMessage(plugin.hMainWindow, WM_WA_IPC, 0, IPC_UPDTITLE);
  342. }
  343. /** if it's a local file or an HTTP asset with content-length
  344. ** we verify that the FLV has codecs we can decode
  345. ** depending on settings, we will do one of the following with unsupport assets
  346. ** 1) Play anyway (e.g. an H.264 video might play audio only)
  347. ** 2) Use in_swf to play back
  348. ** 3) skip
  349. **/
  350. if (needCodecCheck == CODEC_CHECK_UNSURE)
  351. {
  352. FLVHeader *header = reader.GetHeader();
  353. if (header)
  354. {
  355. needCodecCheck=CODEC_CHECK_NONE;
  356. if (!reader.IsStreaming())
  357. {
  358. if (header->hasVideo)
  359. needCodecCheck |= CODEC_CHECK_VIDEO;
  360. if (header->hasAudio)
  361. needCodecCheck |= CODEC_CHECK_AUDIO;
  362. }
  363. if (!header->hasAudio)
  364. {
  365. video_only=true;
  366. video_clock.Start();
  367. }
  368. }
  369. }
  370. if (needCodecCheck)
  371. {
  372. FrameData frameData;
  373. if (reader.GetFrame(codecCheckFrame, frameData))
  374. {
  375. FLVStreamHeader &frameHeader = frameData.header;
  376. if ((needCodecCheck & CODEC_CHECK_AUDIO) && frameHeader.type == FLV::FRAME_TYPE_AUDIO)
  377. {
  378. reader.Seek(frameData.location+15); // TODO: check for -1 return value
  379. uint8_t data[1] = {0};
  380. FLVAudioHeader audioHeader;
  381. size_t bytesRead = reader.Read(data, 1);
  382. if (audioHeader.Read(data, bytesRead))
  383. {
  384. if (Audio_IsSupported(audioHeader.format))
  385. {
  386. needCodecCheck &= ~CODEC_CHECK_AUDIO;
  387. }
  388. else
  389. {
  390. needCodecCheck &= ~CODEC_CHECK_AUDIO;
  391. missingCodecs|=CODEC_CHECK_AUDIO;
  392. video_only=true;
  393. }
  394. }
  395. }
  396. if ((needCodecCheck & CODEC_CHECK_VIDEO) && frameHeader.type == FLV::FRAME_TYPE_VIDEO)
  397. {
  398. reader.Seek(frameData.location+15); // TODO: check for -1 return value
  399. uint8_t data[1] = {0};
  400. FLVVideoHeader videoHeader;
  401. size_t bytesRead = reader.Read(data, 1);
  402. if (videoHeader.Read(data, bytesRead))
  403. {
  404. if (Video_IsSupported(videoHeader.format))
  405. {
  406. needCodecCheck &= ~CODEC_CHECK_VIDEO;
  407. }
  408. else
  409. {
  410. needCodecCheck &= ~CODEC_CHECK_VIDEO;
  411. missingCodecs|=CODEC_CHECK_VIDEO;
  412. }
  413. }
  414. }
  415. codecCheckFrame++;
  416. }
  417. else if (reader.IsEOF())
  418. break;
  419. else if (WaitForSingleObject(killswitch, 55) == WAIT_OBJECT_0)
  420. break;
  421. }
  422. if (needCodecCheck)
  423. continue; // don't start decoding until we've done our codec check
  424. if (missingCodecs)
  425. {
  426. // use in_swf to play this one
  427. if (CheckSWF())
  428. {
  429. HANDLE mainThread = WASABI_API_APP->main_getMainThreadHandle();
  430. if (mainThread)
  431. {
  432. reader.Kill();
  433. Audio_Close();
  434. Video_Stop();
  435. Video_Close();
  436. QueueUserAPC(SWFAPC, mainThread,0);
  437. CloseHandle(mainThread);
  438. return 0;
  439. }
  440. }
  441. else
  442. {
  443. FLVHeader *header = reader.GetHeader();
  444. if (header)
  445. {
  446. bool can_play_something = false;
  447. if (header->hasVideo && !(missingCodecs & CODEC_CHECK_VIDEO))
  448. can_play_something = true; // we can play video
  449. else if (header->hasAudio && !(missingCodecs & CODEC_CHECK_AUDIO))
  450. can_play_something = true; // we can play audio
  451. if (can_play_something)
  452. {
  453. missingCodecs=false;
  454. continue;
  455. }
  456. }
  457. break; // no header or no codecs at all, bail out
  458. }
  459. }
  460. /* --- End Codec Check --- */
  461. FrameData frameData;
  462. if (reader.GetFrame(i, frameData))
  463. {
  464. i++;
  465. uint8_t data[2] = {0};
  466. FLVStreamHeader &frameHeader = frameData.header;
  467. reader.Seek(frameData.location+15); // TODO: check for -1 return value
  468. switch (frameHeader.type)
  469. {
  470. default:
  471. #ifdef _DEBUG
  472. DebugBreak();
  473. #endif
  474. break;
  475. case FLV::FRAME_TYPE_AUDIO: // audio
  476. first_frame_parsed=true;
  477. if (m_need_seek == -1 || !Audio_DecoderReady())
  478. {
  479. FLVAudioHeader audioHeader;
  480. size_t bytesRead = reader.Read(data, 1);
  481. if (audioHeader.Read(data, bytesRead))
  482. {
  483. size_t dataSize = frameHeader.dataSize - 1;
  484. uint8_t *audiodata = (uint8_t *)calloc(dataSize, sizeof(uint8_t));
  485. if (audiodata)
  486. {
  487. bytesRead = reader.Read(audiodata, dataSize);
  488. if (bytesRead != dataSize)
  489. break;
  490. if (!reader.IsStreaming())
  491. {
  492. if (first_timestamp == -1)
  493. first_timestamp = frameHeader.timestamp;
  494. last_timestamp = frameHeader.timestamp;
  495. last_timestamp = plugin.outMod->GetWrittenTime();
  496. }
  497. OnAudio(reader, audiodata, dataSize, audioHeader);
  498. free(audiodata);
  499. }
  500. }
  501. }
  502. break;
  503. case FLV::FRAME_TYPE_VIDEO: // video
  504. first_frame_parsed=true;
  505. if (m_need_seek == -1 || !Video_DecoderReady())
  506. {
  507. FLVVideoHeader videoHeader;
  508. size_t bytesRead = reader.Read(data, 1);
  509. if (videoHeader.Read(data, bytesRead))
  510. {
  511. size_t dataSize = frameHeader.dataSize - 1;
  512. uint8_t *videodata = (uint8_t *)calloc(dataSize, sizeof(uint8_t));
  513. if (videodata)
  514. {
  515. bytesRead = reader.Read(videodata, dataSize);
  516. if (bytesRead != dataSize)
  517. {
  518. free(videodata);
  519. break;
  520. }
  521. if (!OnVideo(videodata, dataSize, videoHeader.format, frameHeader.timestamp))
  522. free(videodata);
  523. }
  524. }
  525. }
  526. break;
  527. case FLV::FRAME_TYPE_METADATA: // metadata
  528. {
  529. first_frame_parsed=true;
  530. size_t dataSize = frameHeader.dataSize;
  531. uint8_t *metadatadata= (uint8_t *)calloc(dataSize, sizeof(uint8_t));
  532. if (metadatadata)
  533. {
  534. size_t bytesRead = reader.Read(metadatadata, dataSize);
  535. if (bytesRead != dataSize)
  536. {
  537. free(metadatadata);
  538. break;
  539. }
  540. FLVMetadata metadata;
  541. metadata.Read(metadatadata, dataSize);
  542. for ( FLVMetadata::Tag *tag : metadata.tags )
  543. {
  544. if (!_wcsicmp(tag->name.str, L"onMetaData"))
  545. {
  546. AMFType *amf_stream_title;
  547. amf_stream_title = tag->parameters->array[L"streamTitle"];
  548. if (amf_stream_title && amf_stream_title->type == AMFType::TYPE_STRING)
  549. {
  550. AMFString *stream_title_string = (AMFString *)amf_stream_title;
  551. Nullsoft::Utility::AutoLock stream_lock(stream_title_guard);
  552. free(stream_title);
  553. stream_title = _wcsdup(stream_title_string->str);
  554. PostMessage(plugin.hMainWindow, WM_WA_IPC, 0, IPC_UPDTITLE);
  555. }
  556. AMFType *w, *h;
  557. w = tag->parameters->array[L"width"];
  558. h = tag->parameters->array[L"height"];
  559. if (w && h)
  560. {
  561. width = (int)AMFGetDouble(w);
  562. height = (int)AMFGetDouble(h);
  563. }
  564. AMFType *duration;
  565. duration=tag->parameters->array[L"duration"];
  566. if (duration)
  567. {
  568. hasDuration=true;
  569. plugin.is_seekable=1;
  570. g_length = (int)(AMFGetDouble(duration)*1000.0);
  571. }
  572. // grab the data rate. we'll need this to determine a good pre-buffer.
  573. AMFType *videoDataRate, *audioDataRate;
  574. videoDataRate=tag->parameters->array[L"videodatarate"];
  575. audioDataRate=tag->parameters->array[L"audiodatarate"];
  576. if (videoDataRate || audioDataRate)
  577. {
  578. dataRate_audio = audioDataRate?AMFGetDouble(audioDataRate):0.0;
  579. dataRate_video = videoDataRate?AMFGetDouble(videoDataRate):0.0;
  580. dataRate = dataRate_audio + dataRate_video;
  581. if (dataRate < 1.0f)
  582. dataRate = 720.0f;
  583. plugin.SetInfo((int)dataRate, -1, -1, 1);
  584. prebuffer = (uint64_t)(dataRate * PRE_BUFFER_MS / 8.0);
  585. if (prebuffer > PRE_BUFFER_MAX)
  586. prebuffer=PRE_BUFFER_MAX;
  587. PostMessage(plugin.hMainWindow, WM_WA_IPC, 0, IPC_UPDTITLE);
  588. }
  589. }
  590. flvCOM.MetadataCallback(tag);
  591. }
  592. free(metadatadata);
  593. }
  594. }
  595. break;
  596. }
  597. }
  598. else if (reader.IsEOF())
  599. break;
  600. else if (WaitForSingleObject(killswitch, 55) == WAIT_OBJECT_0)
  601. break;
  602. }
  603. reader.SignalKill();
  604. if (WaitForSingleObject(killswitch, 0) != WAIT_OBJECT_0)
  605. {
  606. outputter.Write(0,0);
  607. outputter.WaitWhilePlaying();
  608. if (WaitForSingleObject(killswitch, 0) != WAIT_OBJECT_0)
  609. PostMessage(plugin.hMainWindow, WM_WA_MPEG_EOF, 0, 0);
  610. }
  611. SetEvent(killswitch);
  612. Video_Stop();
  613. Video_Close();
  614. reader.Kill();
  615. Audio_Close();
  616. return 0;
  617. }