MediaPlayer.Mod 54 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515
  1. MODULE MediaPlayer; (** AUTHOR "PL/staubesv"; PURPOSE "Media Player"; *)
  2. (**
  3. *
  4. * History:
  5. *
  6. * 15.02.2006 Set UpdateInterval to 500ms, optionally open player window in current view,
  7. * added SetEofAction & EofHandler for testing purposes, improved closing behaviour (staubesv)
  8. *
  9. * TODOs:
  10. * - reuse filler threads
  11. * - optimize filler threads seek procedure (first look at already decoded pictures before flushing them - maybe we can use them)
  12. * - Does the player need to be able to play standalone at all? (Remove GUI related code from module?)
  13. * - implement/finish drop frame mechanism
  14. * - improve SetPos
  15. * - Don't change volume of audio play channels
  16. * - USE same position info for both audio and video!!! (milliseconds)
  17. *)
  18. IMPORT
  19. SoundDevices, Codecs, KernelLog, Streams, Commands, Kernel, Modules, WMTransitions,
  20. WMRectangles, WMGraphics, WMWindowManager, Raster, Strings;
  21. CONST
  22. (* Result codes *)
  23. Ok* = 0;
  24. CouldNotOpenStream* = 1; (* Could not open the specified ressource as stream *)
  25. AudioNotCompatible* = 2; (* Audio decoder found but not compatible *)
  26. VideoNotCompatible* = 3; (* Video decoder found but not compatible *)
  27. DemuxNotCompatible* = 4; (* Demultiplexer found but not compatible *)
  28. NoVideoDecoder* = 5;
  29. NoAudioDecoder* = 6;
  30. NoDecoders* = 7;
  31. WrongParameters* = 8;
  32. (** Player States *)
  33. NotReady* = 1; (* No files opened, not playing anything *)
  34. Ready* = 2; (* Player is ready to play *)
  35. Playing* = 3; (* Player is playing a video *)
  36. Paused* = 4; (* Player is paused *)
  37. Stopped* = 5; (* Player is stopped *)
  38. InTransition* = 7; (* Transition between two videos *)
  39. Finished* = 9; (* Finished video/audio *)
  40. Closed* = 10; (* Player has been closed *)
  41. Error* = 99; (* Player is in error state *)
  42. (* Next state field *)
  43. NoRequest = 0;
  44. (* Audio buffers *)
  45. AudioBufferSize = 288;
  46. AudioBuffers = 160;
  47. AudioConstantDelay = 100; (* Guessed time from playchannel.Start until audio is played *)
  48. (* How many video frames may filler thread decode ahead? *)
  49. VBUFFERS = 10;
  50. (* Interval in millisecond when update is called (it is, however, always called when the state of the player changes *)
  51. UpdateInterval = 500;
  52. (* Should the player window be forced to be always fullscreen? *)
  53. ForceFullscreen = FALSE;
  54. (* Should the player window be forced to open in the default view? (Makes sense when operating over VNC) *)
  55. ForceDefaultView = FALSE;
  56. (* Time in milliseconds when pointer should disappear. 0 = Cursor always hidden *)
  57. PointerInvisibleAfter = 2000;
  58. (* Gather performance data. Will be displayed on kernel log each time a filler is closed (loaded other video / quit) *)
  59. PerformanceStats = FALSE;
  60. TraceNone = {};
  61. TracePlayer = {1};
  62. TraceOpen = {2}; (* Get new context *)
  63. TraceFiller = {3}; (* Video filler thread *)
  64. TraceTransitions = {4}; (* Video transitions *)
  65. TraceStates = {5}; (* Player states *)
  66. TraceRendering = {6}; (* Per frame rendering stats *)
  67. TraceEof = {7}; (* Trace calls to end of file handlers *)
  68. Trace = TraceNone;
  69. Debug = TRUE;
  70. TYPE
  71. (* video buffer *)
  72. VideoBuffer = WMGraphics.Image;
  73. (* buffer pool for the video frames *)
  74. VideoBufferPool = OBJECT
  75. VAR
  76. head, num: LONGINT;
  77. buffer: POINTER TO ARRAY OF VideoBuffer;
  78. PROCEDURE &Init*(n: LONGINT);
  79. BEGIN
  80. head := 0; num := 0; NEW(buffer, n)
  81. END Init;
  82. PROCEDURE Add(x: VideoBuffer);
  83. BEGIN {EXCLUSIVE}
  84. AWAIT(num # LEN(buffer));
  85. buffer[(head+num) MOD LEN(buffer)] := x;
  86. INC(num)
  87. END Add;
  88. PROCEDURE Remove(): VideoBuffer;
  89. VAR x: VideoBuffer;
  90. BEGIN {EXCLUSIVE}
  91. AWAIT(num # 0);
  92. x := buffer[head];
  93. head := (head+1) MOD LEN(buffer);
  94. DEC(num);
  95. RETURN x
  96. END Remove;
  97. PROCEDURE NofBuffers(): LONGINT;
  98. BEGIN {EXCLUSIVE}
  99. RETURN num
  100. END NofBuffers;
  101. END VideoBufferPool;
  102. TYPE
  103. KeyEventHandler* = PROCEDURE {DELEGATE} (ucs : LONGINT; flags : SET; keysym : LONGINT);
  104. PointerDownHandler* = PROCEDURE {DELEGATE} (x, y : LONGINT; keys : SET);
  105. (** default Player Window where the video is played *)
  106. PlayerWindow* = OBJECT(WMWindowManager.DoubleBufferWindow)
  107. VAR
  108. player : Player;
  109. rect : WMRectangles.Rectangle;
  110. videoWidth, videoHeight : LONGINT;
  111. (* Fullscreen functionality related *)
  112. fullscreen- : BOOLEAN;
  113. lastFrame : WMGraphics.Image;
  114. posX, posY : LONGINT; (* Last window position in windowed-mode *)
  115. (* Pointer invisible functionality related *)
  116. timer : Kernel.Timer;
  117. lastTimestamp, timestamp : LONGINT; (* Timemark when mouse pointer was moved last time *)
  118. (* External handlers for key/pointer events. CURRENTLY ACCESS IS NOT SYNCHRONIZED! *)
  119. extPointerDownHandler* : PointerDownHandler;
  120. extKeyEventHandler* : KeyEventHandler;
  121. (* Active object control *)
  122. alive, dead : BOOLEAN;
  123. PROCEDURE &New*(w, h : LONGINT; alpha : BOOLEAN; player : Player; autoHideCursor : BOOLEAN);
  124. BEGIN
  125. SELF.player := player; videoWidth := w; videoHeight := h; posX := 100; posY := 100;
  126. rect := WMRectangles.MakeRect(0, 0, w, h);
  127. Init(w, h, alpha);
  128. manager := WMWindowManager.GetDefaultManager ();
  129. IF ForceDefaultView THEN
  130. manager.Add(posX, posY, SELF, {WMWindowManager.FlagFrame});
  131. ELSE
  132. WMWindowManager.AddWindow(SELF, posX, posY);
  133. END;
  134. manager.SetFocus(SELF);
  135. SetTitle(WMWindowManager.NewString("Video Panel"));
  136. SetIcon(WMGraphics.LoadImage("WMIcons.tar://MediaPlayer.png", TRUE));
  137. IF autoHideCursor & (PointerInvisibleAfter > 0) THEN
  138. NEW(timer);
  139. alive := TRUE; dead := FALSE;
  140. SetPointerVisible(TRUE);
  141. ELSE
  142. alive := FALSE; dead := TRUE;
  143. SetPointerVisible(FALSE);
  144. END;
  145. fullscreen := FALSE;
  146. END New;
  147. (** Toggle between fullscreen and window mode *)
  148. PROCEDURE ToggleFullscreen*;
  149. VAR view : WMWindowManager.ViewPort; width, height : LONGINT;
  150. BEGIN
  151. IF fullscreen THEN
  152. ReInit(videoWidth, videoHeight);
  153. manager.SetWindowSize(SELF, videoWidth, videoHeight);
  154. rect := WMRectangles.MakeRect(0, 0, videoWidth, videoHeight);
  155. manager.SetWindowPos(SELF, posX, posY);
  156. IF lastFrame # NIL THEN ShowFrame(lastFrame); END;
  157. fullscreen := FALSE;
  158. ELSE
  159. posX := bounds.l; posY := bounds.t;
  160. view := WMWindowManager.GetDefaultView();
  161. width := ENTIER(view.range.r - view.range.l);
  162. height := ENTIER(view.range.b - view.range.t);
  163. ReInit(width, height);
  164. manager.SetWindowSize(SELF, width, height);
  165. manager.SetWindowPos(SELF, ENTIER(view.range.l), ENTIER(view.range.t));
  166. rect := WMRectangles.MakeRect(0, 0, width, height);
  167. IF lastFrame # NIL THEN ShowFrame(lastFrame); END;
  168. fullscreen := TRUE;
  169. END;
  170. END ToggleFullscreen;
  171. (* Overwrite draw procedure because we do not want any interpolation *)
  172. PROCEDURE Draw*(canvas : WMGraphics.Canvas; w, h, q : LONGINT);
  173. BEGIN
  174. Draw^(canvas, w, h, 0);
  175. END Draw;
  176. PROCEDURE Close*;
  177. BEGIN
  178. player.Close;
  179. alive := FALSE; IF timer # NIL THEN timer.Wakeup; END;
  180. BEGIN {EXCLUSIVE} AWAIT(dead); END;
  181. Close^;
  182. END Close;
  183. (* Key Handler *)
  184. PROCEDURE KeyEvent*(ucs : LONGINT; flags : SET; keysym : LONGINT);
  185. BEGIN
  186. IF extKeyEventHandler # NIL THEN extKeyEventHandler(ucs, flags, keysym); END;
  187. IF keysym = 0FF50H THEN (* Cursor Home *)
  188. player.ToggleFullScreen(NIL, NIL)
  189. END;
  190. END KeyEvent;
  191. PROCEDURE ShowBlack*;
  192. VAR rect : WMRectangles.Rectangle;
  193. BEGIN
  194. Raster.Clear(img);
  195. Invalidate(rect);
  196. END ShowBlack;
  197. PROCEDURE ShowFrame*(frame : WMGraphics.Image);
  198. VAR s, d : WMRectangles.Rectangle; h, w : LONGINT;
  199. BEGIN
  200. BEGIN {EXCLUSIVE} (* Don't execute the same time as SELF.ReInit does *)
  201. lastFrame := frame;
  202. IF (img.width = frame.width) & (img.height = frame.height) THEN
  203. canvas.DrawImage(0, 0, frame, WMGraphics.ModeCopy);
  204. d := WMRectangles.MakeRect(0, 0, img.width, img.height)
  205. ELSE
  206. s := WMRectangles.MakeRect(0, 0, frame.width, frame.height);
  207. IF (img.width/frame.width) < (img.height/frame.height) THEN
  208. h := ENTIER(frame.height/frame.width*img.width);
  209. d := WMRectangles.MakeRect(0, (img.height- h) DIV 2, img.width, img.height - (img.height - h) DIV 2)
  210. ELSE
  211. w := ENTIER(frame.width/frame.height*img.height);
  212. d := WMRectangles.MakeRect((img.width - w) DIV 2, 0, img.width - (img.width - w) DIV 2, img.height)
  213. END;
  214. canvas.ScaleImage(frame, s, d, WMGraphics.ModeCopy, 0)
  215. END;
  216. END;
  217. Swap;
  218. Invalidate(d);
  219. END ShowFrame;
  220. (* Make pointer visible when it is moved *)
  221. PROCEDURE PointerMove*(x, y : LONGINT; keys : SET);
  222. BEGIN
  223. IF PointerInvisibleAfter > 0 THEN
  224. lastTimestamp := Kernel.GetTicks();
  225. SetPointerVisible(TRUE);
  226. END;
  227. END PointerMove;
  228. PROCEDURE PointerDown*(x, y : LONGINT; keys : SET);
  229. BEGIN
  230. IF PointerInvisibleAfter > 0 THEN
  231. lastTimestamp := Kernel.GetTicks();
  232. SetPointerVisible(TRUE);
  233. IF keys # {2} THEN ToggleFullscreen; END;
  234. IF extPointerDownHandler # NIL THEN extPointerDownHandler(x, y, keys); END;
  235. END;
  236. END PointerDown;
  237. PROCEDURE SetPointerVisible(visible : BOOLEAN);
  238. BEGIN (* Since pointer move messages are not just sent once, we don't need to synchronize access to the pointerVisible field *)
  239. IF visible THEN
  240. SetPointerInfo(manager.pointerStandard);
  241. ELSE
  242. SetPointerInfo(manager.pointerNull);
  243. END;
  244. END SetPointerVisible;
  245. BEGIN {ACTIVE}
  246. IF PointerInvisibleAfter < 1 THEN alive := FALSE; END;
  247. WHILE alive DO (* Make pointer invisible when it is not moved for a certain amount of time *)
  248. timer.Sleep(PointerInvisibleAfter + 10);
  249. timestamp := Kernel.GetTicks();
  250. IF (timestamp - lastTimestamp >= PointerInvisibleAfter) THEN
  251. SetPointerVisible(FALSE);
  252. END;
  253. END;
  254. BEGIN {EXCLUSIVE} dead := TRUE; END;
  255. END PlayerWindow;
  256. TYPE
  257. (* buffer filler thread *)
  258. Filler = OBJECT
  259. VAR
  260. videoDecoder : Codecs.VideoDecoder;
  261. vBufferPool : VideoBufferPool;
  262. readyBufferPool : VideoBufferPool;
  263. vBuffer : VideoBuffer;
  264. blackBuffer : VideoBuffer;
  265. drop : LONGINT;
  266. frame : VideoBuffer;
  267. alive, positionChanged : BOOLEAN;
  268. (* Performance statistics *)
  269. framesDecoded : LONGINT;
  270. min, max, tot : LONGINT;
  271. perf : LONGINT;
  272. dropped : LONGINT;
  273. PROCEDURE &New*(videoWidth, videoHeight : LONGINT; videoDecoder : Codecs.VideoDecoder);
  274. VAR i : LONGINT;
  275. BEGIN
  276. (* empty buffers *)
  277. NEW(vBufferPool, VBUFFERS);
  278. FOR i := 0 TO VBUFFERS-1 DO
  279. NEW(vBuffer);
  280. Raster.Create(vBuffer, videoWidth, videoHeight, Raster.BGR565);
  281. vBufferPool.Add(vBuffer)
  282. END;
  283. (* full buffers *)
  284. NEW(readyBufferPool, VBUFFERS);
  285. (* temp buffer *)
  286. NEW(blackBuffer); NEW(frame);
  287. Raster.Create(blackBuffer, videoWidth, videoHeight, Raster.BGR565);
  288. Raster.Create(frame, videoWidth, videoHeight, Raster.BGR565);
  289. SELF.videoDecoder := videoDecoder;
  290. alive := TRUE; positionChanged := FALSE;
  291. IF PerformanceStats THEN
  292. min := MAX(LONGINT);
  293. END;
  294. END New;
  295. (* Returns the next Buffer ready to be played *)
  296. PROCEDURE GetNextBuffer(): VideoBuffer;
  297. BEGIN
  298. IF readyBufferPool.NofBuffers() > 0 THEN
  299. RETURN readyBufferPool.Remove()
  300. ELSE
  301. INC(dropped);
  302. RETURN blackBuffer;
  303. END;
  304. END GetNextBuffer;
  305. (* Puts the buffer back into the empty BufferPool *)
  306. PROCEDURE ReturnBuffer(buf : VideoBuffer);
  307. BEGIN
  308. IF buf # blackBuffer THEN
  309. vBufferPool.Add(buf)
  310. END
  311. END ReturnBuffer;
  312. PROCEDURE DropFrames(n : LONGINT);
  313. BEGIN
  314. drop := n
  315. END DropFrames;
  316. PROCEDURE GetPos(): LONGINT;
  317. BEGIN {EXCLUSIVE}
  318. RETURN videoDecoder.GetCurrentFrame()
  319. END GetPos;
  320. PROCEDURE SeekAndGetFrame(pos: LONGINT; VAR f : WMGraphics.Image; VAR res : LONGINT);
  321. BEGIN {EXCLUSIVE}
  322. ASSERT(frame # NIL);
  323. WHILE readyBufferPool.NofBuffers() > 0 DO (* flush Buffer *)
  324. vBufferPool.Add(readyBufferPool.Remove())
  325. END;
  326. videoDecoder.SeekFrame(pos, TRUE, res);
  327. videoDecoder.Next; videoDecoder.Next;
  328. videoDecoder.Render(frame);
  329. f := frame;
  330. videoDecoder.SeekFrame(pos, TRUE, res);
  331. videoDecoder.Next;
  332. IF videoDecoder.HasMoreData() THEN positionChanged := TRUE; END;
  333. END SeekAndGetFrame;
  334. PROCEDURE SeekFrame(pos : LONGINT; isKeyFrame : BOOLEAN; VAR res : LONGINT);
  335. BEGIN {EXCLUSIVE}
  336. WHILE readyBufferPool.NofBuffers() > 0 DO (* flush Buffer *)
  337. vBufferPool.Add(readyBufferPool.Remove())
  338. END;
  339. videoDecoder.SeekFrame(pos, TRUE, res);
  340. videoDecoder.Next;
  341. IF videoDecoder.HasMoreData() THEN positionChanged := TRUE; END;
  342. END SeekFrame;
  343. (* Returns the number of decoded frames available *)
  344. PROCEDURE NofFullBuffers() : LONGINT;
  345. BEGIN
  346. RETURN readyBufferPool.NofBuffers()
  347. END NofFullBuffers;
  348. (* Terminate the filler process, but still grant access to already decoded frames *)
  349. PROCEDURE Stop;
  350. BEGIN {EXCLUSIVE}
  351. IF Trace * TraceFiller # {} THEN KernelLog.String("MediaPlayer: Filler stopped."); KernelLog.Ln; END;
  352. alive := FALSE;
  353. END Stop;
  354. (* Terminate the filler process *)
  355. PROCEDURE Close;
  356. BEGIN {EXCLUSIVE}
  357. IF Trace * TraceFiller # {} THEN KernelLog.String("MediaPlayer: Closing filler..."); KernelLog.Ln; END;
  358. alive := FALSE;
  359. (* To establish await conditions required to exit the active objects body *)
  360. IF readyBufferPool.NofBuffers() > 0 THEN vBufferPool.Add(readyBufferPool.Remove()); END;
  361. END Close;
  362. BEGIN {ACTIVE}
  363. WHILE alive DO
  364. IF videoDecoder.HasMoreData() THEN
  365. vBuffer := vBufferPool.Remove(); (* Will block of no buffers available *)
  366. BEGIN {EXCLUSIVE}
  367. IF alive & videoDecoder.HasMoreData() THEN
  368. IF PerformanceStats THEN
  369. perf := Kernel.GetTicks();
  370. END;
  371. videoDecoder.Next;
  372. videoDecoder.Render(vBuffer);
  373. IF PerformanceStats THEN
  374. perf := Kernel.GetTicks() - perf;
  375. IF perf < min THEN min := perf;
  376. ELSIF perf > max THEN max := perf;
  377. END;
  378. INC(tot, perf);
  379. INC(framesDecoded);
  380. END;
  381. END;
  382. END;
  383. readyBufferPool.Add(vBuffer);
  384. ELSE
  385. BEGIN {EXCLUSIVE} AWAIT(positionChanged OR ~alive); positionChanged := FALSE; END;
  386. END;
  387. END;
  388. IF Trace * TraceFiller # {} THEN KernelLog.String("MediaPlayer: Filler closed."); KernelLog.Ln; END;
  389. IF PerformanceStats THEN
  390. IF framesDecoded > 0 THEN
  391. KernelLog.String("MediaPlayer: Decoded "); KernelLog.Int(framesDecoded, 0); KernelLog.String(" frames in "); KernelLog.Int(tot, 0);
  392. KernelLog.String("ms (min: "); KernelLog.Int(min, 0);
  393. KernelLog.String(", avg: "); KernelLog.Int(tot DIV framesDecoded, 0); KernelLog.String(", max: "); KernelLog.Int(max, 0); KernelLog.String(")");
  394. KernelLog.String(", "); KernelLog.Int(dropped, 0); KernelLog.String(" frames not decoded in time"); KernelLog.Ln;
  395. END;
  396. END;
  397. END Filler;
  398. TYPE
  399. Setup* = POINTER TO RECORD
  400. uri- : ARRAY 256 OF CHAR;
  401. hasAudio-, hasVideo- : BOOLEAN; (* Does the opened ressource contain audio and video? *)
  402. canSeek- : BOOLEAN; (* Is seeking supported? *)
  403. maxTime- : LONGINT; (* Duration of Video/Audio in 1/10 sec *)
  404. (* If hasVideo *)
  405. width-, height- : LONGINT; (* width and height of video frames if applicable *)
  406. mspf- : LONGINT; (* milliseconds per frame *)
  407. maxFrames- : LONGINT;
  408. (* If hasAudio *)
  409. channels-, bits-, rate-: LONGINT; (* Number of audio channels, resolution and rate *)
  410. END;
  411. (* The context record stores all information needed to play the associated ressource *)
  412. Context = POINTER TO RECORD
  413. uri : ARRAY 256 OF CHAR; (* Ressource to be played *)
  414. hasVideo, hasAudio : BOOLEAN; (* Does the ressource contain video and/or audio? *)
  415. canSeek : BOOLEAN; (* Do the video and the audio stream support seeking? *)
  416. pos, oldPos : LONGINT; (* Current/last position in stream *)
  417. (* Only accessible if hasVideo = TRUE *)
  418. video : Codecs.VideoDecoder;
  419. maxFrames, maxTime : LONGINT;
  420. width, height, mspf : LONGINT; (* width, heigth & milliseconds per frame of video *)
  421. filler : Filler; (* Video Filler Thread *)
  422. vBuffer : VideoBuffer; (* Video Buffer Object *)
  423. (* Only accessible if hasAudio = TRUE *)
  424. audio : Codecs.AudioDecoder;
  425. channels, bits, rate : LONGINT; (* Number of audio channels, their resolution (bits) and sampling rate (rate) (reported by GetAudioInfo() *)
  426. posRate : LONGINT; (* StreamInfo reports other rate than GetAudioInfo(). Use this value for calculating audio positions *)
  427. aBuffer : SoundDevices.Buffer;
  428. channel : SoundDevices.Channel;
  429. bufferpool : SoundDevices.BufferPool;
  430. delay : LONGINT; (* Delay in milliseconds induced by using audio buffers *)
  431. (* Transition related *)
  432. transition : WMTransitions.TransitionFade; (* Transition Object*)
  433. transitionFrame : LONGINT; (* Current frame in Transition *)
  434. transitionDuration : LONGINT; (* Number of frames the transition endures *)
  435. transitionImg : VideoBuffer; (* Transition target video buffer *)
  436. black : VideoBuffer; (* Black frame *)
  437. END;
  438. TYPE
  439. EofProc = PROCEDURE {DELEGATE} (sender, data: ANY);
  440. (* Decouples end-of-file handlers from media player main loop. This enables eof handlers to call methods
  441. * of the media player *)
  442. EofHandler = OBJECT
  443. VAR
  444. proc : EofProc;
  445. player : Player;
  446. alive, dead, called : BOOLEAN;
  447. PROCEDURE Call;
  448. BEGIN {EXCLUSIVE}
  449. called := TRUE;
  450. END Call;
  451. PROCEDURE Terminate;
  452. BEGIN
  453. IF Trace * TraceEof # {} THEN KernelLog.String("MediaPlayer: Terminating EOF handler."); KernelLog.Ln; END;
  454. BEGIN {EXCLUSIVE} alive := FALSE; END;
  455. (* Release obj lock to force condition evaluation *)
  456. BEGIN {EXCLUSIVE} AWAIT(dead); END;
  457. END Terminate;
  458. PROCEDURE &New*(player : Player);
  459. BEGIN
  460. SELF.player := player; alive := TRUE; dead := FALSE;
  461. END New;
  462. BEGIN {ACTIVE}
  463. WHILE alive DO
  464. BEGIN {EXCLUSIVE} AWAIT(~alive OR called); called := FALSE; END;
  465. IF alive & (proc # NIL) THEN
  466. IF Trace * TraceEof # {} THEN KernelLog.String("MediaPlayer: Call EOF procedure."); KernelLog.Ln; END;
  467. proc(player, NIL);
  468. END;
  469. END;
  470. BEGIN {EXCLUSIVE} dead := TRUE; END;
  471. END EofHandler;
  472. (**
  473. * Player Object
  474. * The body of the active object manages the state of the player. If a client wants to change the player state, it
  475. * issues a state change request using (indirectly) RequestState.
  476. *)
  477. Player*= OBJECT
  478. VAR
  479. (* Access the fields 'state', 'current' and 'next' only from within exclusive regions! *)
  480. state : LONGINT; (* Current state of player *)
  481. current, next : Context;
  482. (* State change request fields. Access via RequestState and GetRequestedState. *)
  483. nextState : LONGINT;
  484. nextContext : Context;
  485. requestProcessed : BOOLEAN;
  486. lock : BOOLEAN;
  487. console* : BOOLEAN; (* Should error messages be displayed on console? *)
  488. (* Audio *)
  489. soundDevice : SoundDevices.Driver; (* Audio Device *)
  490. mixerChannel, pcmChannel, mChannel : SoundDevices.MixerChannel; (* Audio Mixer Channel *)
  491. channelName : ARRAY 128 OF CHAR; (* Audio Mixer Channel Name *)
  492. (* player window *)
  493. pw : PlayerWindow; (* Video Window *)
  494. (* Timing *)
  495. timer : Kernel.Timer;
  496. tickStart : LONGINT; (* TimeMark Start of Playing *)
  497. tickDelay : LONGINT; (* Number of milliseconds the decoding was too slow *)
  498. lastUpdate : LONGINT; (* Last time the update procedure was called *)
  499. videoFramesPlayed : LONGINT; (* Video Frames played since TimeMarkStart *)
  500. (* Milliseconds per frame; used for time synchronisation. TODO: Currently, the mspf value of the 'current' context is used.
  501. what if 'next' context has another mspf value and is concurrently played (transition)? *)
  502. mspf : LONGINT;
  503. (* Delegates *)
  504. setup* : PROCEDURE {DELEGATE} (data : Setup); (* init for GUI *)
  505. update* : PROCEDURE {DELEGATE} (state, pos, maxpos, displayTime: LONGINT); (* update for GUI *)
  506. eof : EofHandler; (* fired on End of File *)
  507. (** -- Player Controls ----------------------------------------------- *)
  508. (* Open the given uri *)
  509. PROCEDURE Open*(CONST uri : ARRAY OF CHAR; VAR msg : ARRAY OF CHAR; VAR res : WORD);
  510. VAR context : Context;
  511. BEGIN
  512. context := GetContext(uri, msg, res);
  513. IF (res # Ok) OR (context = NIL) THEN
  514. IF Debug OR console THEN
  515. KernelLog.String("MediaPlayer: Could not open file "); KernelLog.String(uri);
  516. KernelLog.String("(res: "); KernelLog.Int(res, 0); KernelLog.String(", "); KernelLog.String(msg); KernelLog.String(")");
  517. KernelLog.Ln;
  518. END;
  519. RETURN;
  520. END;
  521. RequestState(Ready, context);
  522. IF Trace * TraceOpen # {} THEN KernelLog.String("MediaPlayer: Opened "); KernelLog.String(uri); KernelLog.Ln; END;
  523. END Open;
  524. PROCEDURE Play*;
  525. BEGIN
  526. RequestState(Playing, NIL);
  527. END Play;
  528. (* pos & duration set in number of video frames *)
  529. PROCEDURE DoTransition*(CONST uri: ARRAY OF CHAR; pos, duration : LONGINT; VAR msg : ARRAY OF CHAR; VAR res : WORD);
  530. VAR context : Context; audioPos : LONGINT;
  531. BEGIN
  532. IF Trace * TraceTransitions # {} THEN
  533. KernelLog.String("MediaPlayer: Doing a Transition to "); KernelLog.String(uri); KernelLog.String(" (Duration: ");
  534. KernelLog.Int(duration, 0); KernelLog.String(" frames)"); KernelLog.Ln;
  535. END;
  536. IF (duration < 1) OR (pos < 0) THEN
  537. IF Debug OR console THEN KernelLog.String("MediaPlayer: Warning: DoTransition: Pos or duration value adjusted."); KernelLog.Ln; END;
  538. IF pos < 0 THEN pos := 0; END;
  539. IF duration < 1 THEN duration := 1; END;
  540. END;
  541. context := GetContext(uri, msg, res);
  542. IF context = NIL THEN
  543. IF Debug OR console THEN KernelLog.String("MediaPlayer: Could not get context for transition: "); KernelLog.String(msg); KernelLog.Ln; END;
  544. RETURN;
  545. END;
  546. IF context.hasVideo & (pos > context.maxFrames) THEN
  547. IF Debug OR console THEN
  548. KernelLog.String("MediaPlayer: Warning: DoTransition: Pos value clipped to maxFrames: ");
  549. KernelLog.Int(context.maxFrames, 0); KernelLog.Ln;
  550. END;
  551. pos := context.maxFrames;
  552. (* Continue *)
  553. END;
  554. IF context.hasVideo THEN
  555. context.filler.SeekFrame(pos, TRUE, res);
  556. context.pos := res; context.oldPos := res-1;
  557. IF context.hasAudio THEN (* search the according Audio *)
  558. (* The value 12 corresponds to cdSize in the AVI - WaveFormatStructure - should be given via Codecs *)
  559. audioPos := ENTIER(context.maxTime/10*context.posRate*(res/context.maxFrames) -
  560. ENTIER(context.maxTime/10*context.posRate*(res/context.maxFrames)) MOD 12);
  561. IF audioPos < 0 THEN audioPos := 0 END;
  562. END;
  563. (* Init Transition *)
  564. NEW(context.transition); context.transition.Init(context.width, context.height);
  565. NEW(context.transitionImg); Raster.Create(context.transitionImg, context.width, context.height, Raster.BGR565);
  566. NEW(context.black); Raster.Create(context.black, context.width, context.height, Raster.BGR565);
  567. context.transitionFrame := 0;
  568. context.transitionDuration := duration;
  569. END;
  570. IF context.hasAudio & (soundDevice # NIL) THEN
  571. context.audio.SeekSample(audioPos, FALSE, res);
  572. context.channel.SetVolume(0);
  573. context.channel.Start;
  574. END;
  575. IF Trace * TraceTransitions # {} THEN
  576. KernelLog.String("MediaPlayer: Transition to pos (next keyframe): "); KernelLog.Int(context.pos, 0);
  577. KernelLog.String(" (wanted: "); KernelLog.Int(pos, 0); KernelLog.String(")"); KernelLog.Ln;
  578. END;
  579. RequestState(InTransition, context);
  580. END DoTransition;
  581. PROCEDURE Stop*;
  582. BEGIN
  583. RequestState(Stopped, NIL);
  584. END Stop;
  585. PROCEDURE Pause*;
  586. BEGIN
  587. RequestState(Paused, NIL);
  588. END Pause;
  589. (* Get in 1/10 sec. Returns -1 if information is not available. *)
  590. PROCEDURE GetPos*(): LONGINT;
  591. VAR context : Context; res : WORD;
  592. BEGIN {EXCLUSIVE}
  593. res := -1;
  594. context := current;
  595. IF context # NIL THEN
  596. IF context.hasVideo THEN res := 10*current.filler.GetPos() DIV (1000 DIV current.mspf);
  597. ELSIF current.hasAudio THEN res := current.audio.GetCurrentTime();
  598. END;
  599. END;
  600. RETURN res;
  601. END GetPos;
  602. (* Set position *)
  603. PROCEDURE SetPos*(pos: LONGINT);
  604. VAR current : Context; audioPos, res : LONGINT; img : WMGraphics.Image;
  605. BEGIN {EXCLUSIVE}
  606. IF pos < 0 THEN
  607. IF Debug THEN KernelLog.String("MediaPlayer: Warning: Setpos to "); KernelLog.Int(pos, 0); KernelLog.String("!?!"); KernelLog.Ln; END;
  608. pos := 0;
  609. END;
  610. current := SELF.current;
  611. IF (current # NIL) & current.canSeek THEN
  612. IF current.hasVideo THEN
  613. IF pos > current.maxFrames THEN
  614. IF Debug THEN KernelLog.String("MediaPlayer: Warning: Setpos to "); KernelLog.Int(pos, 0); KernelLog.String(" (>MaxFrames)!?! "); KernelLog.Ln; END;
  615. pos := current.maxFrames;
  616. END;
  617. current.filler.SeekAndGetFrame(pos, img, res);
  618. IF img # NIL THEN
  619. IF pw # NIL THEN pw.ShowFrame(img); END;
  620. END;
  621. current.pos := res;
  622. current.oldPos := current.pos-1;
  623. IF current.hasAudio THEN (* search the according Audio *)
  624. (* The value 12 corresponds to cdSize in the AVI - WaveFormatStructure - should be given via Codecs *)
  625. audioPos := ENTIER(current.maxTime/10*current.posRate*(res/current.maxFrames) -
  626. ENTIER(current.maxTime/10*current.posRate*(res/current.maxFrames)) MOD 12);
  627. IF audioPos < 0 THEN audioPos := 0 END;
  628. current.audio.SeekSample(audioPos, FALSE, res);
  629. END;
  630. ELSIF current.hasAudio THEN (* search audio only *)
  631. pos := ENTIER(pos / 10 * current.posRate);
  632. current.audio.SeekSample(pos, FALSE, res);
  633. pos := current.audio.GetCurrentTime();
  634. current.pos := pos;
  635. current.oldPos := current.pos-1;
  636. END;
  637. (* IF update # NIL THEN update(state, pos, current.maxFrames, pos) END *)
  638. END;
  639. END SetPos;
  640. PROCEDURE SetEofAction(proc : EofProc);
  641. BEGIN {EXCLUSIVE}
  642. IF eof = NIL THEN NEW(eof, SELF); END;
  643. eof.proc := proc;
  644. END SetEofAction;
  645. (* Creates a new Player Instance *)
  646. PROCEDURE &New*;
  647. VAR i : LONGINT;
  648. BEGIN
  649. NEW(timer);
  650. IF (SoundDevices.devices.Get("") # NIL) THEN
  651. soundDevice := SoundDevices.GetDefaultDevice();
  652. (* set mixerchannel to max output *)
  653. soundDevice.GetMixerChannel(0, mixerChannel); (* global mixer channel *)
  654. mixerChannel.SetVolume(255);
  655. (* find PCM MixerChannel *)
  656. FOR i := 0 TO soundDevice.GetNofMixerChannels() - 1 DO
  657. soundDevice.GetMixerChannel(i, mChannel);
  658. mChannel.GetName(channelName);
  659. IF channelName = "PCMOut" THEN pcmChannel := mChannel; pcmChannel.SetVolume(255) END
  660. END;
  661. END;
  662. eof := NIL;
  663. SetState(NotReady);
  664. END New;
  665. PROCEDURE Acquire;
  666. BEGIN {EXCLUSIVE}
  667. AWAIT(lock = FALSE);
  668. lock := TRUE;
  669. END Acquire;
  670. PROCEDURE Release;
  671. BEGIN {EXCLUSIVE}
  672. lock := FALSE;
  673. END Release;
  674. (* Request to media player to go into the specified state *)
  675. PROCEDURE RequestState(state : LONGINT; context : Context);
  676. BEGIN
  677. Acquire;
  678. BEGIN {EXCLUSIVE}
  679. requestProcessed := FALSE;
  680. IF nextState # NoRequest THEN (* Skip the already scheduled state change *)
  681. IF nextContext # NIL THEN FreeContext(nextContext); END;
  682. END;
  683. nextState := state;
  684. nextContext := context;
  685. END;
  686. (* Release the lock to evaluate the 'nextState' await condition which in turn will
  687. lead to requestProcessed to be set. *)
  688. BEGIN {EXCLUSIVE}
  689. AWAIT(requestProcessed OR (state >= Closed));
  690. END;
  691. Release;
  692. END RequestState;
  693. PROCEDURE GetRequestedState(VAR state : LONGINT; VAR context : Context);
  694. BEGIN {EXCLUSIVE}
  695. state := nextState; nextState := NoRequest;
  696. context := nextContext; nextContext := NIL;
  697. requestProcessed := TRUE;
  698. END GetRequestedState;
  699. PROCEDURE SetState(state : LONGINT);
  700. BEGIN {EXCLUSIVE}
  701. IF Trace * TraceStates # {} THEN KernelLog.String("MediaPlayer: Set state to "); KernelLog.Int(state, 0); KernelLog.Ln; END;
  702. SELF.state := state;
  703. END SetState;
  704. PROCEDURE GetState() : LONGINT;
  705. BEGIN {EXCLUSIVE}
  706. RETURN state;
  707. END GetState;
  708. PROCEDURE ToggleFullScreen*(sender, data : ANY);
  709. BEGIN {EXCLUSIVE}
  710. IF (pw # NIL) & (~ForceFullscreen) THEN pw.ToggleFullscreen; END;
  711. END ToggleFullScreen;
  712. PROCEDURE CheckWindow(context : Context);
  713. VAR oldPw : PlayerWindow;
  714. BEGIN
  715. IF context.hasVideo THEN
  716. IF (pw # NIL) & pw.fullscreen THEN
  717. (* do nothing *)
  718. ELSIF (pw = NIL) OR (pw.GetWidth() # context.width) OR (pw.GetHeight() # context.height) THEN
  719. oldPw := pw;
  720. NEW(pw, context.width, context.height, FALSE, SELF, TRUE);
  721. IF ForceFullscreen THEN pw.ToggleFullscreen; END;
  722. IF oldPw # NIL THEN oldPw.Close; END;
  723. END;
  724. ELSIF pw # NIL THEN
  725. pw.Close;
  726. END;
  727. END CheckWindow;
  728. (* reset the tickTimer *)
  729. PROCEDURE InitTime;
  730. BEGIN
  731. tickStart := Kernel.GetTicks(); tickDelay := 0;
  732. videoFramesPlayed := 0;
  733. END InitTime;
  734. (* Allocate ressources required for playing the audio/video stream *)
  735. PROCEDURE GetContext(CONST uri : ARRAY OF CHAR; VAR msg : ARRAY OF CHAR; VAR res : WORD) : Context;
  736. VAR
  737. in, audioStream, videoStream : Streams.Reader;
  738. demux : Codecs.AVDemultiplexer;
  739. audioDecoder : Codecs.AudioDecoder; videoDecoder : Codecs.VideoDecoder;
  740. streamInfo : Codecs.AVStreamInfo;
  741. nofStreams, type, maxFrames, maxTime, width, height, mspf : LONGINT;
  742. channels, rate, bits: LONGINT;
  743. buffer : SoundDevices.Buffer;
  744. hasAudio, hasVideo : BOOLEAN;
  745. audioCanSeek, videoCanSeek : BOOLEAN;
  746. name, ext : ARRAY 256 OF CHAR;
  747. context : Context;
  748. i : LONGINT;
  749. BEGIN
  750. IF Trace * TraceOpen # {} THEN KernelLog.String("MediaPlayer: Get decoders for: "); KernelLog.String(uri); KernelLog.Ln; END;
  751. in := Codecs.OpenInputStream(uri);
  752. IF (in = NIL) THEN
  753. res := CouldNotOpenStream; COPY("Can't open stream: ", msg); Strings.Append(msg, uri);
  754. IF Debug OR console THEN KernelLog.String("MediaPlayer: "); KernelLog.String(msg); KernelLog.Ln; END;
  755. RETURN NIL;
  756. END;
  757. Strings.GetExtension(uri, name, ext); (* split uri into name & extension *)
  758. Strings.UpperCase(ext); (* convert extension to UpperCase for Codecs *)
  759. (* find Demultiplexer *)
  760. demux := Codecs.GetDemultiplexer(ext);
  761. IF demux = NIL THEN
  762. IF Trace * TraceOpen # {} THEN KernelLog.String("MediaPlayer: No Demux found: "); KernelLog.String(ext); KernelLog.Ln; END;
  763. (* no demuxable file / no suitable demux *)
  764. audioDecoder := Codecs.GetAudioDecoder(ext);
  765. IF (audioDecoder # NIL) THEN
  766. audioDecoder.Open(in, res);
  767. IF (res # Codecs.ResOk) THEN
  768. res := AudioNotCompatible; COPY("Audio stream not compatible: ", msg); Strings.Append(msg, uri);
  769. IF Debug OR console THEN KernelLog.String("MediaPlayer: "); KernelLog.String(msg); KernelLog.Ln; END;
  770. RETURN NIL;
  771. END;
  772. hasAudio := TRUE;
  773. audioCanSeek := audioDecoder.CanSeek();
  774. IF in IS Codecs.FileInputStream THEN (* Set the Stream Length in Bytes, needed for some Functions in some Decoders *)
  775. audioDecoder.SetStreamLength(in(Codecs.FileInputStream).f.Length());
  776. END;
  777. ELSE
  778. videoDecoder := Codecs.GetVideoDecoder(ext);
  779. IF (videoDecoder # NIL) THEN
  780. videoDecoder.Open(in, res);
  781. IF (res # Codecs.ResOk) THEN
  782. res := VideoNotCompatible; COPY("Video stream not compatible: ", msg); Strings.Append(msg, uri);
  783. IF Debug OR console THEN KernelLog.String("MediaPlayer: "); KernelLog.String(msg); KernelLog.Ln; END;
  784. RETURN NIL;
  785. END;
  786. hasVideo := TRUE;
  787. videoCanSeek := videoDecoder.CanSeek();
  788. ELSE
  789. res := NoDecoders; COPY("No decoder available for: ", msg); Strings.Append(msg, uri);
  790. IF Debug OR console THEN KernelLog.String("MediaPlayer: "); KernelLog.String(msg); KernelLog.Ln; END;
  791. RETURN NIL;
  792. END;
  793. END;
  794. ELSE
  795. IF Trace * TraceOpen # {} THEN KernelLog.String("MediaPlayer: Demux found: "); KernelLog.String(ext); KernelLog.Ln; END;
  796. demux.Open(in, res);
  797. IF (res # Codecs.ResOk) THEN
  798. res := DemuxNotCompatible; COPY("Demux stream not compatible: ", msg); Strings.Append(msg, uri);
  799. IF Debug OR console THEN KernelLog.String("MediaPlayer: "); KernelLog.String(msg); KernelLog.Ln; END;
  800. RETURN NIL;
  801. END;
  802. nofStreams := demux.GetNumberOfStreams();
  803. IF Trace * TraceOpen # {} THEN KernelLog.String("MediaPlayer: Number of Streams: "); KernelLog.Int(nofStreams, 0); KernelLog.Ln; END;
  804. FOR i := 0 TO nofStreams-1 DO
  805. IF Trace * TraceOpen # {} THEN KernelLog.String("MediaPlayer: Processing Stream: "); KernelLog.Int(i, 0); END;
  806. type := demux.GetStreamType(i);
  807. IF Trace * TraceOpen # {} THEN KernelLog.String(" with Type: "); KernelLog.Int(type, 0); KernelLog.Ln; END;
  808. IF (type = Codecs.STVideo) THEN (* Video Stream *)
  809. videoStream := demux.GetStream(i);
  810. videoStream(Codecs.DemuxStream).Open(demux, i);
  811. streamInfo := demux.GetStreamInfo(i);
  812. videoDecoder := Codecs.GetVideoDecoder(streamInfo.contentType);
  813. IF (videoDecoder = NIL) THEN
  814. res := NoVideoDecoder; COPY("No Decoder for video format: ", msg); Strings.Append(msg, streamInfo.contentType);
  815. IF Debug OR console THEN KernelLog.String("MediaPlayer: "); KernelLog.String(msg); KernelLog.Ln; END;
  816. hasVideo := FALSE;
  817. ELSE
  818. videoDecoder.Open(videoStream, res);
  819. IF (res # Codecs.ResOk) THEN
  820. res := VideoNotCompatible; COPY("Video stream not compatible: ", msg); Strings.Append(msg, uri);
  821. IF Debug OR console THEN KernelLog.String("MediaPlayer: "); KernelLog.String(msg); KernelLog.Ln; END;
  822. hasVideo := FALSE;
  823. ELSE
  824. videoCanSeek := videoDecoder.CanSeek();
  825. maxFrames := streamInfo.frames;
  826. IF streamInfo.rate # 0 THEN maxTime := 10 * streamInfo.frames DIV streamInfo.rate; (* 1/10 sec *)
  827. ELSE videoCanSeek := FALSE;
  828. END;
  829. hasVideo := TRUE;
  830. END;
  831. END;
  832. ELSIF (type = Codecs.STAudio) THEN (* Audio Stream *)
  833. audioStream := demux.GetStream(i);
  834. audioStream(Codecs.DemuxStream).Open(demux, i);
  835. streamInfo := demux.GetStreamInfo(i);
  836. audioDecoder := Codecs.GetAudioDecoder(streamInfo.contentType);
  837. IF (audioDecoder = NIL) THEN
  838. res := NoAudioDecoder; COPY("No Decoder for audio format: ", msg); Strings.Append(msg, streamInfo.contentType);
  839. IF Debug OR console THEN KernelLog.String("MediaPlayer: "); KernelLog.String(msg); KernelLog.Ln; END;
  840. hasAudio := FALSE;
  841. ELSE
  842. audioDecoder.Open(audioStream, res);
  843. IF (res # Codecs.ResOk) THEN
  844. res := AudioNotCompatible; COPY("Audio stream not compatible: ", msg); Strings.Append(msg, uri);
  845. IF Debug OR console THEN KernelLog.String("MediaPlayer: "); KernelLog.String(msg); KernelLog.Ln; END;
  846. hasAudio := FALSE;
  847. ELSE
  848. audioCanSeek := audioDecoder.CanSeek();
  849. hasAudio := TRUE;
  850. END;
  851. END;
  852. ELSE
  853. IF Trace * TraceOpen # {} THEN KernelLog.String("MediaPlayer: Unknown Stream Type: "); KernelLog.Int(type, 0); KernelLog.Ln; END;
  854. END;
  855. END;
  856. END;
  857. IF hasVideo OR hasAudio THEN
  858. NEW(context);
  859. COPY(uri, context.uri);
  860. IF hasVideo THEN
  861. context.hasVideo := TRUE;
  862. context.video := videoDecoder;
  863. context.maxFrames := maxFrames; context.maxTime := maxTime;
  864. videoDecoder.GetVideoInfo(width, height, mspf);
  865. context.width := width; context.height := height; context.mspf := mspf;
  866. NEW(context.filler, width, height, videoDecoder);
  867. END;
  868. IF hasAudio & (soundDevice # NIL)THEN
  869. context.hasAudio := TRUE;
  870. context.audio := audioDecoder;
  871. (* Calculate delay induced by audio buffering *)
  872. IF (channels # 0) & (bits # 0) & (rate # 0) THEN
  873. context.delay := ENTIER(AudioBuffers * AudioBufferSize * 8 (* Buffersize in bits *) / (channels * bits * rate) (* bit rate of audio stream *)) + 1 ;
  874. ELSE
  875. context.delay := 50; (* guess *)
  876. END;
  877. context.delay := context.delay + AudioConstantDelay;
  878. IF Trace * TraceOpen # {} THEN KernelLog.String("Audio delay: "); KernelLog.Int(context.delay, 0); KernelLog.String("ms"); KernelLog.Ln; END;
  879. NEW(context.bufferpool, AudioBuffers);
  880. FOR i := 0 TO AudioBuffers-1 DO
  881. NEW(buffer); buffer.len := AudioBufferSize; NEW(buffer.data, AudioBufferSize);
  882. context.bufferpool.Add(buffer);
  883. END;
  884. audioDecoder.GetAudioInfo(channels, rate, bits);
  885. context.channels := channels; context.rate := rate; context.bits := bits;
  886. (* UGLY: AVStreamInfo reports different rate as GetAudioInfo does. *)
  887. IF (audioStream # NIL) & (audioStream IS Codecs.DemuxStream) THEN
  888. context.posRate := audioStream(Codecs.DemuxStream).streamInfo.rate;
  889. END;
  890. IF ~hasVideo THEN
  891. context.maxTime := audioDecoder.GetTotalSamples() DIV rate * 10;
  892. context.maxFrames := maxTime;
  893. END;
  894. audioDecoder.SeekSample(0, FALSE, res);(* what if stream not seekable? *)
  895. soundDevice.OpenPlayChannel(context.channel, rate, bits, channels, SoundDevices.FormatPCM, res);
  896. IF context.channel = NIL THEN
  897. IF Debug OR console THEN KernelLog.String("MediaPlayer: Could not open audio play channel."); KernelLog.Ln; END;
  898. ELSE
  899. context.channel.RegisterBufferListener(context.bufferpool.Add);
  900. context.channel.SetVolume(255);
  901. END;
  902. END;
  903. context.canSeek := (~hasVideo OR videoCanSeek) & (~hasAudio OR audioCanSeek);
  904. ELSE
  905. res := NoDecoders; COPY("No demux/decoder found for ", msg); Strings.Append(msg, uri);
  906. IF Debug OR console THEN KernelLog.String("MediaPlayer: "); KernelLog.String(msg); KernelLog.Ln; END;
  907. END;
  908. IF Trace * TraceOpen # {} THEN
  909. KernelLog.String("Context opened: Maxtime: "); KernelLog.Int(context.maxTime, 0);
  910. KernelLog.String(", maxFrames: "); KernelLog.Int(context.maxFrames, 0); KernelLog.Ln;
  911. END;
  912. RETURN context;
  913. END GetContext;
  914. PROCEDURE FreeContext(context : Context);
  915. BEGIN
  916. IF context # NIL THEN
  917. IF context.filler # NIL THEN context.filler.Close; context.filler := NIL; END;
  918. IF context.channel # NIL THEN context.channel.Close; context.channel := NIL; END;
  919. END;
  920. END FreeContext;
  921. PROCEDURE Loop(sender, data: ANY);
  922. BEGIN
  923. IF Trace * TraceEof # {} THEN KernelLog.String("MediaPlayer: EOF Loop."); KernelLog.Ln; END;
  924. Stop; Play;
  925. END Loop;
  926. PROCEDURE Quit(sender, data: ANY);
  927. BEGIN
  928. IF Trace * TraceEof # {} THEN KernelLog.String("MediaPlayer: EOF Quit."); KernelLog.Ln; END;
  929. Close;
  930. END Quit;
  931. PROCEDURE RenderAudio(c : Context);
  932. BEGIN (* no concurrency allowed *)
  933. ASSERT(c # NIL);
  934. WHILE c.hasAudio & c.audio.HasMoreData() & (c.bufferpool.NofBuffers() > 0) DO
  935. c.aBuffer := c.bufferpool.Remove();
  936. c.audio.FillBuffer(c.aBuffer);
  937. c.channel.QueueBuffer(c.aBuffer);
  938. IF ~c.hasVideo THEN
  939. c.pos := c.audio.GetCurrentTime();
  940. END
  941. END;
  942. END RenderAudio;
  943. (* Render the next video frame of the specified context *)
  944. PROCEDURE RenderVideo(c : Context);
  945. BEGIN
  946. ASSERT((c # NIL) & (c.hasVideo));
  947. c.vBuffer := c.filler.GetNextBuffer();
  948. IF pw # NIL THEN pw.ShowFrame(c.vBuffer) END;
  949. c.filler.ReturnBuffer(c.vBuffer);
  950. INC(videoFramesPlayed); c.oldPos := c.pos; INC(c.pos);
  951. END RenderVideo;
  952. (* Render transition from context 'from' to context 'to' *)
  953. PROCEDURE RenderVideoTransition(from, to : Context);
  954. VAR temp : VideoBuffer;
  955. BEGIN
  956. ASSERT((to # NIL) & (to.transition # NIL) & (to.transitionImg # NIL) & (to.black # NIL));
  957. INC(to.transitionFrame);
  958. IF Trace * TraceTransitions # {} THEN
  959. KernelLog.String("MediaPlayer: Transition Frame ");
  960. KernelLog.Int(to.transitionFrame, 0); KernelLog.String(" of "); KernelLog.Int(to.transitionDuration, 0);
  961. KernelLog.Ln;
  962. END;
  963. (* stop filler if enough frames already precalced in buffer *)
  964. IF (from # NIL) THEN
  965. IF from.filler.NofFullBuffers() >= (to.transitionDuration - to.transitionFrame) THEN from.filler.Stop; END;
  966. from.vBuffer := from.filler.GetNextBuffer();
  967. temp := from.vBuffer;
  968. ELSE
  969. temp := to.black;
  970. END;
  971. (* display frame *)
  972. to.vBuffer := to.filler.GetNextBuffer();
  973. to.transition.CalcImage(temp, to.vBuffer, to.transitionImg, to.transitionFrame*255 DIV to.transitionDuration);
  974. IF pw # NIL THEN pw.ShowFrame(to.transitionImg) END;
  975. to.filler.ReturnBuffer(to.vBuffer);
  976. to.oldPos := to.pos; INC(to.pos);
  977. IF from # NIL THEN
  978. from.filler.ReturnBuffer(from.vBuffer);
  979. from.oldPos := from.pos; INC(from.pos);
  980. END;
  981. INC(videoFramesPlayed);
  982. IF to.transitionFrame >= to.transitionDuration THEN
  983. IF (from # NIL) THEN FreeContext(from); from := NIL; END;
  984. IF Trace * TraceTransitions # {} THEN KernelLog.String("MediaPlayer: Transition Finished."); KernelLog.Ln; END;
  985. END;
  986. END RenderVideoTransition;
  987. (* Render video and/or audio *)
  988. PROCEDURE Render(c1, c2 : Context);
  989. VAR
  990. tickIs: LONGINT;
  991. tickShould : LONGINT;
  992. BEGIN
  993. IF c1 # NIL THEN
  994. IF GetState() = InTransition THEN (* Render transition from c2 to c1 *)
  995. IF c2 # NIL THEN
  996. IF c2.hasAudio THEN
  997. RenderAudio(c2);
  998. c2.channel.SetVolume(256 - c1.transitionFrame*(256 DIV c1.transitionDuration));
  999. ELSE
  1000. timer.Sleep(40);
  1001. END;
  1002. END;
  1003. IF c1.hasAudio THEN
  1004. RenderAudio(c1);
  1005. c1.channel.SetVolume(256*c1.transitionFrame DIV c1.transitionDuration);
  1006. END;
  1007. IF c1.hasVideo THEN RenderVideoTransition(c2, c1); END;
  1008. ELSE
  1009. IF c1.hasAudio THEN
  1010. RenderAudio(c1);
  1011. END;
  1012. IF c1.hasVideo THEN
  1013. RenderVideo(c1);
  1014. END;
  1015. END;
  1016. END;
  1017. (* Check time and sleep or drop frames *)
  1018. tickIs := Kernel.GetTicks() - tickStart;
  1019. tickShould := videoFramesPlayed * mspf;
  1020. IF tickIs < tickShould THEN (* We were too fast *)
  1021. timer.Sleep(tickShould-tickIs);
  1022. END;
  1023. IF Trace * TraceRendering # {} THEN
  1024. KernelLog.String("Frame: "); KernelLog.Int(videoFramesPlayed, 0);
  1025. KernelLog.String(" [Is: "); KernelLog.Int(tickIs, 0); KernelLog.String(", Should="); KernelLog.Int(tickShould, 0);
  1026. KernelLog.String(",Sleep: ");
  1027. IF (tickIs < tickShould) THEN
  1028. IF tickDelay < (tickShould-tickIs) THEN
  1029. KernelLog.Int(tickShould - tickIs - tickDelay, 0);
  1030. ELSE
  1031. KernelLog.Int(8, 0);
  1032. END;
  1033. ELSE KernelLog.Int(0, 0);
  1034. END;
  1035. KernelLog.String(", Delay: "); KernelLog.Int(tickDelay, 0);
  1036. KernelLog.String("]"); KernelLog.Ln;
  1037. END;
  1038. END Render;
  1039. PROCEDURE Close*;
  1040. BEGIN
  1041. RequestState(Closed, NIL);
  1042. BEGIN {EXCLUSIVE} AWAIT(state = Closed); END;
  1043. mplayer := NIL;
  1044. IF Trace * TracePlayer # {} THEN KernelLog.String("MediaPlayer closed."); KernelLog.Ln; END;
  1045. END Close;
  1046. PROCEDURE StopIntern;
  1047. VAR img : WMGraphics.Image; res : WORD;
  1048. BEGIN
  1049. FreeContext(next); next := NIL;
  1050. IF current # NIL THEN
  1051. IF current.hasVideo THEN current.filler.SeekAndGetFrame(0, img, res); END;
  1052. IF current.hasAudio THEN current.channel.Stop; current.audio.SeekSample(0, FALSE, res); END;
  1053. current.pos := 0; current.oldPos := -1;
  1054. END;
  1055. IF pw # NIL THEN
  1056. IF img # NIL THEN pw.ShowFrame(img); ELSE pw.ShowBlack; END;
  1057. END;
  1058. END StopIntern;
  1059. PROCEDURE StartPlayIntern;
  1060. VAR res : WORD;
  1061. BEGIN
  1062. IF current # NIL THEN
  1063. IF state = Finished THEN
  1064. current.pos := 0; current.oldPos := -1;
  1065. IF current.hasVideo THEN current.filler.SeekFrame(0, TRUE, res); END;
  1066. IF current.hasAudio THEN current.channel.Stop; current.audio.SeekSample(0, FALSE, res); END;
  1067. END;
  1068. IF current.hasAudio THEN current.channel.SetVolume(255); current.channel.Start; END;
  1069. InitTime;
  1070. END;
  1071. END StartPlayIntern;
  1072. (* Pause the current and next context if applicable *)
  1073. PROCEDURE PauseIntern;
  1074. BEGIN
  1075. IF (current # NIL) & current.hasAudio THEN current.channel.Pause; END;
  1076. IF (next # NIL) & next.hasAudio THEN next.channel.Pause; END;
  1077. END PauseIntern;
  1078. (* Resume playing the curent and next context that are paused *)
  1079. PROCEDURE ResumeIntern;
  1080. VAR audioPos, res : LONGINT;
  1081. BEGIN
  1082. IF (current # NIL) THEN
  1083. IF current.hasVideo THEN
  1084. res := (current.video.GetCurrentFrame()-current.filler.NofFullBuffers());
  1085. IF current.hasAudio THEN
  1086. audioPos := ENTIER(current.maxTime/10*current.posRate*(res/current.maxFrames) -
  1087. ENTIER(current.maxTime/10*current.posRate*(res/current.maxFrames)) MOD 12);
  1088. IF audioPos < 0 THEN audioPos := 0 END;
  1089. current.audio.SeekSample(audioPos, FALSE, res);
  1090. END;
  1091. END;
  1092. IF current.hasAudio THEN current.channel.Start; END;
  1093. END;
  1094. IF (next # NIL) THEN
  1095. IF next.hasVideo THEN
  1096. res := (next.video.GetCurrentFrame()-next.filler.NofFullBuffers());
  1097. IF next.hasAudio THEN
  1098. audioPos := ENTIER(next.maxTime/10*next.posRate*(res/next.maxFrames) -
  1099. ENTIER(next.maxTime/10*next.posRate*(res/next.maxFrames)) MOD 12);
  1100. IF audioPos < 0 THEN audioPos := 0 END;
  1101. next.audio.SeekSample(audioPos, FALSE, res);
  1102. END;
  1103. END;
  1104. IF next.hasAudio THEN next.channel.Start; END;
  1105. END;
  1106. IF (current # NIL) OR (next # NIL) THEN
  1107. InitTime;
  1108. END;
  1109. END ResumeIntern;
  1110. PROCEDURE OpenIntern(nextContext : Context);
  1111. VAR img : WMGraphics.Image; data : Setup; res : WORD;
  1112. BEGIN
  1113. FreeContext(next); next := NIL;
  1114. FreeContext(current); current := nextContext;
  1115. mspf := current.mspf; current.pos := 0; current.oldPos := -1;
  1116. CheckWindow(current);
  1117. IF current.hasVideo THEN
  1118. current.filler.SeekAndGetFrame(0, img, res);
  1119. END;
  1120. IF pw # NIL THEN
  1121. IF img # NIL THEN pw.ShowFrame(img); ELSE pw.ShowBlack; END;
  1122. END;
  1123. IF setup # NIL THEN
  1124. NEW(data);
  1125. data.hasVideo := current.hasVideo;
  1126. data.hasAudio := current.hasAudio;
  1127. data.canSeek := current.canSeek;
  1128. COPY(nextContext.uri, data.uri);
  1129. data.mspf := current.mspf;
  1130. data.maxFrames := current.maxFrames;
  1131. data.maxTime := current.maxTime;
  1132. IF current.hasVideo THEN
  1133. data.width := current.width; data.height := current.height;
  1134. END;
  1135. IF current.hasAudio THEN
  1136. data.channels := current.channels; data.bits := current.bits; data.rate := current.rate;
  1137. END;
  1138. setup(data);
  1139. END;
  1140. END OpenIntern;
  1141. PROCEDURE IsValidStateTransition(from, to : LONGINT) : BOOLEAN;
  1142. VAR res : BOOLEAN;
  1143. BEGIN
  1144. res := FALSE;
  1145. CASE from OF
  1146. |NotReady: IF (to = Ready) OR (to = InTransition) THEN res := TRUE; END;
  1147. |Ready: IF (to = Ready) OR (to = Playing) OR (to = InTransition) THEN res := TRUE; END;
  1148. |Playing: IF (to = Ready) OR (to = Paused) OR (to = Stopped) OR (to = InTransition) THEN res := TRUE; END;
  1149. |Paused: IF (to = Ready) OR (to = Playing) OR (to = Stopped) OR (to = Paused) OR (to = InTransition) THEN res := TRUE; END;
  1150. |Stopped: IF (to = Ready) OR (to = Playing) OR (to = InTransition) THEN res := TRUE; END;
  1151. |Finished: IF (to = Ready) OR (to = Playing) OR (to = Stopped) OR (to = InTransition) THEN res := TRUE; END;
  1152. |InTransition: IF (to = Ready) OR (to = Paused) OR (to = Stopped) OR (to = InTransition) THEN res := TRUE; END;
  1153. |Error: (* Do not execute commands anymore *)
  1154. |Closed: (* Do not execute commands anymore *)
  1155. ELSE
  1156. IF Debug THEN KernelLog.String("MediaPlayer: Start state of state transition not known."); KernelLog.Ln; END;
  1157. END;
  1158. IF (to = Error) OR (to = Closed) THEN res := TRUE; END;
  1159. RETURN res;
  1160. END IsValidStateTransition;
  1161. (* Pre-Condition: (state = Playing) OR (state = InTransition) OR (nextState # NoRequest) *)
  1162. PROCEDURE EvaluateState;
  1163. VAR
  1164. oldState : LONGINT;
  1165. isValid : BOOLEAN; nextState : LONGINT; nextContext : Context;
  1166. audioFinished, videoFinished : BOOLEAN;
  1167. currentTime : LONGINT; callUpdate : BOOLEAN;
  1168. temp : LONGINT;
  1169. BEGIN
  1170. GetRequestedState(nextState, nextContext); oldState := state;
  1171. IF (nextState # NoRequest) THEN
  1172. isValid := IsValidStateTransition(state, nextState);
  1173. IF isValid THEN
  1174. CASE nextState OF
  1175. NoRequest: (* Rest in current state *)
  1176. |Ready: (* Abort whatever the player is doing and go to 'Ready' state *)
  1177. OpenIntern(nextContext);
  1178. SetState(Ready);
  1179. |Playing:
  1180. StartPlayIntern; SetState(Playing);
  1181. |Paused:
  1182. IF state = Paused THEN
  1183. ResumeIntern; SetState(Playing);
  1184. ELSE
  1185. PauseIntern; SetState(Paused);
  1186. END;
  1187. |Stopped:
  1188. StopIntern; SetState(Stopped);
  1189. |InTransition:
  1190. FreeContext(next); next := current;
  1191. current := nextContext;
  1192. mspf := current.mspf;
  1193. CheckWindow(current);
  1194. InitTime;
  1195. SetState(InTransition);
  1196. |Closed:
  1197. FreeContext(current); current := NIL;
  1198. FreeContext(next); next := NIL;
  1199. FreeContext(nextContext); nextContext := NIL;
  1200. SetState(Closed);
  1201. ELSE
  1202. IF Debug THEN KernelLog.String("MediaPlayer: Warning: Ignore request to set state to: "); KernelLog.Int(nextState, 0); KernelLog.Ln; END;
  1203. END;
  1204. END;
  1205. IF Trace * TraceStates # {} THEN
  1206. KernelLog.String("MediaPlayer: Request state transition from '");
  1207. KernelLog.Int(oldState, 0); KernelLog.String("' to '"); KernelLog.Int(nextState, 0); KernelLog.String("': ");
  1208. IF isValid THEN KernelLog.String("Valid (New state: "); KernelLog.Int(state, 0); KernelLog.String(")");
  1209. ELSE KernelLog.String("Invalid (Rejected)");
  1210. END;
  1211. KernelLog.Ln;
  1212. END;
  1213. END;
  1214. IF nextState = NoRequest THEN (* Check whether current video is still playing *)
  1215. IF (state = InTransition) & (current.transitionFrame >= current.transitionDuration) THEN (* Transition Finished *)
  1216. SetState(Playing);
  1217. END;
  1218. audioFinished := FALSE; videoFinished := FALSE;
  1219. IF current = NIL THEN
  1220. audioFinished := TRUE; videoFinished := TRUE;
  1221. ELSE
  1222. IF ~current.hasVideo OR (current.hasVideo & ~current.video.HasMoreData() & (current.filler.NofFullBuffers() <= 0)) THEN videoFinished := TRUE; END;
  1223. IF ~current.hasAudio OR (current.hasAudio & ~current.audio.HasMoreData()) THEN audioFinished := TRUE; END;
  1224. IF Debug THEN
  1225. (* IF (current.hasVideo = current.hasAudio) & (videoFinished # audioFinished) THEN
  1226. KernelLog.String("MediaPlayer: Audio & Video not finished at the same time."); KernelLog.Ln;
  1227. END; *)
  1228. END;
  1229. END;
  1230. IF (current = NIL) OR ((current # NIL) & current.hasVideo & videoFinished) OR ((current # NIL) & (~current.hasVideo) & audioFinished) THEN
  1231. IF state # Finished THEN
  1232. IF Trace * TracePlayer # {} THEN
  1233. KernelLog.String("MediaPlayer: Finished playing: ");
  1234. IF (current = NIL) THEN KernelLog.String("No context.");
  1235. ELSE
  1236. IF videoFinished THEN KernelLog.String("[Video finished]"); END;
  1237. IF audioFinished THEN KernelLog.String("[Audio finished]"); END;
  1238. END;
  1239. KernelLog.Ln;
  1240. END;
  1241. IF eof # NIL THEN eof.Call; END;
  1242. END;
  1243. SetState(Finished);
  1244. END
  1245. END;
  1246. IF current # NIL THEN
  1247. IF current.hasVideo THEN
  1248. currentTime := 10*current.pos DIV (1000 DIV current.mspf);
  1249. ELSE
  1250. currentTime := current.audio.GetCurrentTime();
  1251. END;
  1252. temp := Kernel.GetTicks();
  1253. callUpdate := (update # NIL) & ((state = Finished) OR (nextState # NoRequest) OR (temp - lastUpdate >= UpdateInterval));
  1254. IF callUpdate THEN lastUpdate := temp; update(state, current.pos, current.maxFrames, currentTime); END;
  1255. ELSE
  1256. IF update # NIL THEN update(state, 0, 0, 0); END;
  1257. END;
  1258. END EvaluateState;
  1259. BEGIN {ACTIVE}
  1260. WHILE state < Closed DO
  1261. (* Synchronization to player commands *)
  1262. BEGIN {EXCLUSIVE} AWAIT((state = Playing) OR (state = InTransition) OR (state >= Closed) OR (nextState # NoRequest)); END;
  1263. IF state < Closed THEN
  1264. (* Within this IF statement we have exlusive access to all Context objects *)
  1265. (* Render next video/audio frame *)
  1266. IF nextState = NoRequest THEN Render(current, next); END;
  1267. (* State management (process state change requests and current state *)
  1268. EvaluateState;
  1269. END;
  1270. END;
  1271. FINALLY
  1272. FreeContext(current); current := NIL;
  1273. FreeContext(next); next := NIL;
  1274. FreeContext(nextContext); nextContext := NIL;
  1275. IF eof # NIL THEN eof.Terminate; END;
  1276. IF pw # NIL THEN pw.Close; pw := NIL; END;
  1277. SetState(Closed);
  1278. END Player;
  1279. VAR mplayer : Player;
  1280. (** Command line user interface *)
  1281. (** Play the specified video/audio file *)
  1282. PROCEDURE Open*(context : Commands.Context); (** <filename> ~ *)
  1283. VAR filename, msg : ARRAY 256 OF CHAR; res : WORD;
  1284. BEGIN {EXCLUSIVE}
  1285. context.arg.String(filename);
  1286. IF mplayer = NIL THEN NEW(mplayer); END;
  1287. mplayer.Open(filename, msg, res);
  1288. IF res = Streams.Ok THEN
  1289. mplayer.Play;
  1290. ELSE
  1291. context.error.String("MediaPlayer: Could not open file: "); context.error.String(filename);
  1292. context.error.String(" (res: "); context.error.Int(res, 0); context.error.String(", ");
  1293. context.error.String(msg); context.error.String(")"); context.error.Ln;
  1294. END;
  1295. END Open;
  1296. (** Do a transition to the specified video/audio file (of the specified duration) *)
  1297. PROCEDURE TransitionTo*(context : Commands.Context); (** <filename> [transitionDuration] ~ *)
  1298. VAR filename, msg : ARRAY 256 OF CHAR; duration : LONGINT; res : WORD;
  1299. BEGIN {EXCLUSIVE}
  1300. context.arg.SkipWhitespace; context.arg.String(filename);
  1301. context.arg.SkipWhitespace; context.arg.Int(duration, FALSE);
  1302. IF (context.arg.res # Streams.Ok) OR (duration < 1) THEN duration := 25; END;
  1303. IF mplayer # NIL THEN
  1304. mplayer.DoTransition(filename, 0, duration, msg, res);
  1305. IF res # Ok THEN
  1306. context.error.String("MediaPlayer.DoTransition Error (res: "); context.error.Int(res, 0);
  1307. context.error.String(", "); context.error.String(msg); context.error.String(")"); context.error.Ln;
  1308. END;
  1309. ELSE
  1310. NEW(mplayer); mplayer.DoTransition(filename, 0, duration, msg, res);
  1311. IF res # Ok THEN
  1312. context.error.String("MediaPlayer.DoTransition Error (res: "); context.error.Int(res, 0);
  1313. context.error.String(", "); context.error.String(msg); context.error.String(")"); context.error.Ln;
  1314. END;
  1315. END;
  1316. END TransitionTo;
  1317. (** Close the media player and its window *)
  1318. PROCEDURE Close*; (** ~ *)
  1319. BEGIN
  1320. Cleanup;
  1321. END Close;
  1322. (** Set a EOF (end of file) handler, i.e. action to be taken when playing of a ressource finished. *)
  1323. PROCEDURE SetEofAction*(context : Commands.Context); (* [none | loop | quit] ~ *)
  1324. VAR command : ARRAY 32 OF CHAR;
  1325. BEGIN
  1326. IF mplayer # NIL THEN
  1327. context.arg.SkipWhitespace; context.arg.String(command);
  1328. IF Strings.Match("none", command) THEN
  1329. mplayer.eof := NIL;
  1330. context.out.String("MediaPlayer: Set EOF to NIL.");
  1331. ELSIF Strings.Match("loop", command) THEN
  1332. mplayer.SetEofAction(mplayer.Loop);
  1333. context.out.String("MediaPlayer: Set EOF to Loop.");
  1334. ELSIF Strings.Match("quit", command) THEN
  1335. mplayer.SetEofAction(mplayer.Quit);
  1336. context.out.String("MediaPlayer: Set EOF to Quit.");
  1337. ELSE
  1338. context.out.String("MediaPlayer: Command not recognized.");
  1339. END;
  1340. ELSE
  1341. context.error.String("MediaPlayer: Cannot set EOF - player not running."); context.error.Ln;
  1342. END;
  1343. context.out.Ln;
  1344. END SetEofAction;
  1345. PROCEDURE Cleanup;
  1346. BEGIN {EXCLUSIVE}
  1347. IF mplayer # NIL THEN mplayer.Close; mplayer := NIL; END;
  1348. END Cleanup;
  1349. BEGIN
  1350. Modules.InstallTermHandler(Cleanup);
  1351. END MediaPlayer.
  1352. ------------------------------------------------------------
  1353. i810Sound.Install ~
  1354. System.Free MediaPlayer ~
  1355. System.Free WMPlayer MediaPlayer DivXDecoder DivXHelper DivXTypes AVI~
  1356. PC.Compile AVI.Mod DivXTypes.Mod DivXHelper.Mod DivXDecoder.Mod MediaPlayer.Mod WMPlayer.Mod~
  1357. System.Free WMPlayer MediaPlayer ~
  1358. WMPlayer.Open flags.avi~
  1359. MediaPlayer.Open flags.avi~
  1360. MediaPlayer.TransitionTo flags.avi 25 ~
  1361. MediaPlayer.TransitionTo flags.avi 300 ~
  1362. MediaPlayer.Close ~
  1363. MediaPlayer.SetEofAction none ~
  1364. MediaPlayer.SetEofAction loop ~
  1365. MediaPlayer.SetEofAction quit ~