MODULE MediaPlayer;
IMPORT
SoundDevices, Codecs, KernelLog, Streams, Commands, Kernel, Modules, WMTransitions,
WMRectangles, WMGraphics, WMWindowManager, Raster, Strings;
CONST
Ok* = 0;
CouldNotOpenStream* = 1;
AudioNotCompatible* = 2;
VideoNotCompatible* = 3;
DemuxNotCompatible* = 4;
NoVideoDecoder* = 5;
NoAudioDecoder* = 6;
NoDecoders* = 7;
WrongParameters* = 8;
NotReady* = 1;
Ready* = 2;
Playing* = 3;
Paused* = 4;
Stopped* = 5;
InTransition* = 7;
Finished* = 9;
Closed* = 10;
Error* = 99;
NoRequest = 0;
AudioBufferSize = 288;
AudioBuffers = 160;
AudioConstantDelay = 100;
VBUFFERS = 10;
UpdateInterval = 500;
ForceFullscreen = FALSE;
ForceDefaultView = FALSE;
PointerInvisibleAfter = 2000;
PerformanceStats = FALSE;
TraceNone = {};
TracePlayer = {1};
TraceOpen = {2};
TraceFiller = {3};
TraceTransitions = {4};
TraceStates = {5};
TraceRendering = {6};
TraceEof = {7};
Trace = TraceNone;
Debug = TRUE;
TYPE
VideoBuffer = WMGraphics.Image;
VideoBufferPool = OBJECT
VAR
head, num: LONGINT;
buffer: POINTER TO ARRAY OF VideoBuffer;
PROCEDURE &Init*(n: LONGINT);
BEGIN
head := 0; num := 0; NEW(buffer, n)
END Init;
PROCEDURE Add(x: VideoBuffer);
BEGIN {EXCLUSIVE}
AWAIT(num # LEN(buffer));
buffer[(head+num) MOD LEN(buffer)] := x;
INC(num)
END Add;
PROCEDURE Remove(): VideoBuffer;
VAR x: VideoBuffer;
BEGIN {EXCLUSIVE}
AWAIT(num # 0);
x := buffer[head];
head := (head+1) MOD LEN(buffer);
DEC(num);
RETURN x
END Remove;
PROCEDURE NofBuffers(): LONGINT;
BEGIN {EXCLUSIVE}
RETURN num
END NofBuffers;
END VideoBufferPool;
TYPE
KeyEventHandler* = PROCEDURE {DELEGATE} (ucs : LONGINT; flags : SET; keysym : LONGINT);
PointerDownHandler* = PROCEDURE {DELEGATE} (x, y : LONGINT; keys : SET);
PlayerWindow* = OBJECT(WMWindowManager.DoubleBufferWindow)
VAR
player : Player;
rect : WMRectangles.Rectangle;
videoWidth, videoHeight : LONGINT;
fullscreen- : BOOLEAN;
lastFrame : WMGraphics.Image;
posX, posY : LONGINT;
timer : Kernel.Timer;
lastTimestamp, timestamp : LONGINT;
extPointerDownHandler* : PointerDownHandler;
extKeyEventHandler* : KeyEventHandler;
alive, dead : BOOLEAN;
PROCEDURE &New*(w, h : LONGINT; alpha : BOOLEAN; player : Player; autoHideCursor : BOOLEAN);
BEGIN
SELF.player := player; videoWidth := w; videoHeight := h; posX := 100; posY := 100;
rect := WMRectangles.MakeRect(0, 0, w, h);
Init(w, h, alpha);
manager := WMWindowManager.GetDefaultManager ();
IF ForceDefaultView THEN
manager.Add(posX, posY, SELF, {WMWindowManager.FlagFrame});
ELSE
WMWindowManager.AddWindow(SELF, posX, posY);
END;
manager.SetFocus(SELF);
SetTitle(WMWindowManager.NewString("Video Panel"));
SetIcon(WMGraphics.LoadImage("WMIcons.tar://MediaPlayer.png", TRUE));
IF autoHideCursor & (PointerInvisibleAfter > 0) THEN
NEW(timer);
alive := TRUE; dead := FALSE;
SetPointerVisible(TRUE);
ELSE
alive := FALSE; dead := TRUE;
SetPointerVisible(FALSE);
END;
fullscreen := FALSE;
END New;
PROCEDURE ToggleFullscreen*;
VAR view : WMWindowManager.ViewPort; width, height : LONGINT;
BEGIN
IF fullscreen THEN
ReInit(videoWidth, videoHeight);
manager.SetWindowSize(SELF, videoWidth, videoHeight);
rect := WMRectangles.MakeRect(0, 0, videoWidth, videoHeight);
manager.SetWindowPos(SELF, posX, posY);
IF lastFrame # NIL THEN ShowFrame(lastFrame); END;
fullscreen := FALSE;
ELSE
posX := bounds.l; posY := bounds.t;
view := WMWindowManager.GetDefaultView();
width := ENTIER(view.range.r - view.range.l);
height := ENTIER(view.range.b - view.range.t);
ReInit(width, height);
manager.SetWindowSize(SELF, width, height);
manager.SetWindowPos(SELF, ENTIER(view.range.l), ENTIER(view.range.t));
rect := WMRectangles.MakeRect(0, 0, width, height);
IF lastFrame # NIL THEN ShowFrame(lastFrame); END;
fullscreen := TRUE;
END;
END ToggleFullscreen;
PROCEDURE Draw*(canvas : WMGraphics.Canvas; w, h, q : LONGINT);
BEGIN
Draw^(canvas, w, h, 0);
END Draw;
PROCEDURE Close;
BEGIN
player.Close;
alive := FALSE; IF timer # NIL THEN timer.Wakeup; END;
BEGIN {EXCLUSIVE} AWAIT(dead); END;
Close^;
END Close;
PROCEDURE KeyEvent(ucs : LONGINT; flags : SET; keysym : LONGINT);
BEGIN
IF extKeyEventHandler # NIL THEN extKeyEventHandler(ucs, flags, keysym); END;
IF keysym = 0FF50H THEN
player.ToggleFullScreen(NIL, NIL)
END;
END KeyEvent;
PROCEDURE ShowBlack*;
VAR rect : WMRectangles.Rectangle;
BEGIN
Raster.Clear(img);
Invalidate(rect);
END ShowBlack;
PROCEDURE ShowFrame*(frame : WMGraphics.Image);
VAR s, d : WMRectangles.Rectangle; h, w : LONGINT;
BEGIN
BEGIN {EXCLUSIVE}
lastFrame := frame;
IF (img.width = frame.width) & (img.height = frame.height) THEN
canvas.DrawImage(0, 0, frame, WMGraphics.ModeCopy);
d := WMRectangles.MakeRect(0, 0, img.width, img.height)
ELSE
s := WMRectangles.MakeRect(0, 0, frame.width, frame.height);
IF (img.width/frame.width) < (img.height/frame.height) THEN
h := ENTIER(frame.height/frame.width*img.width);
d := WMRectangles.MakeRect(0, (img.height- h) DIV 2, img.width, img.height - (img.height - h) DIV 2)
ELSE
w := ENTIER(frame.width/frame.height*img.height);
d := WMRectangles.MakeRect((img.width - w) DIV 2, 0, img.width - (img.width - w) DIV 2, img.height)
END;
canvas.ScaleImage(frame, s, d, WMGraphics.ModeCopy, 0)
END;
END;
Swap;
Invalidate(d);
END ShowFrame;
PROCEDURE PointerMove(x, y : LONGINT; keys : SET);
BEGIN
IF PointerInvisibleAfter > 0 THEN
lastTimestamp := Kernel.GetTicks();
SetPointerVisible(TRUE);
END;
END PointerMove;
PROCEDURE PointerDown(x, y : LONGINT; keys : SET);
BEGIN
IF PointerInvisibleAfter > 0 THEN
lastTimestamp := Kernel.GetTicks();
SetPointerVisible(TRUE);
IF keys # {2} THEN ToggleFullscreen; END;
IF extPointerDownHandler # NIL THEN extPointerDownHandler(x, y, keys); END;
END;
END PointerDown;
PROCEDURE SetPointerVisible(visible : BOOLEAN);
BEGIN
IF visible THEN
SetPointerInfo(manager.pointerStandard);
ELSE
SetPointerInfo(manager.pointerNull);
END;
END SetPointerVisible;
BEGIN {ACTIVE}
IF PointerInvisibleAfter < 1 THEN alive := FALSE; END;
WHILE alive DO (* Make pointer invisible when it is not moved for a certain amount of time *)
timer.Sleep(PointerInvisibleAfter + 10);
timestamp := Kernel.GetTicks();
IF (timestamp - lastTimestamp >= PointerInvisibleAfter) THEN
SetPointerVisible(FALSE);
END;
END;
BEGIN {EXCLUSIVE} dead := TRUE; END;
END PlayerWindow;
TYPE
Filler = OBJECT
VAR
videoDecoder : Codecs.VideoDecoder;
vBufferPool : VideoBufferPool;
readyBufferPool : VideoBufferPool;
vBuffer : VideoBuffer;
blackBuffer : VideoBuffer;
drop : LONGINT;
frame : VideoBuffer;
alive, positionChanged : BOOLEAN;
framesDecoded : LONGINT;
min, max, tot : LONGINT;
perf : LONGINT;
dropped : LONGINT;
PROCEDURE &New*(videoWidth, videoHeight : LONGINT; videoDecoder : Codecs.VideoDecoder);
VAR i : LONGINT;
BEGIN
NEW(vBufferPool, VBUFFERS);
FOR i := 0 TO VBUFFERS-1 DO
NEW(vBuffer);
Raster.Create(vBuffer, videoWidth, videoHeight, Raster.BGR565);
vBufferPool.Add(vBuffer)
END;
NEW(readyBufferPool, VBUFFERS);
NEW(blackBuffer); NEW(frame);
Raster.Create(blackBuffer, videoWidth, videoHeight, Raster.BGR565);
Raster.Create(frame, videoWidth, videoHeight, Raster.BGR565);
SELF.videoDecoder := videoDecoder;
alive := TRUE; positionChanged := FALSE;
IF PerformanceStats THEN
min := MAX(LONGINT);
END;
END New;
PROCEDURE GetNextBuffer(): VideoBuffer;
BEGIN
IF readyBufferPool.NofBuffers() > 0 THEN
RETURN readyBufferPool.Remove()
ELSE
INC(dropped);
RETURN blackBuffer;
END;
END GetNextBuffer;
PROCEDURE ReturnBuffer(buf : VideoBuffer);
BEGIN
IF buf # blackBuffer THEN
vBufferPool.Add(buf)
END
END ReturnBuffer;
PROCEDURE DropFrames(n : LONGINT);
BEGIN
drop := n
END DropFrames;
PROCEDURE GetPos(): LONGINT;
BEGIN {EXCLUSIVE}
RETURN videoDecoder.GetCurrentFrame()
END GetPos;
PROCEDURE SeekAndGetFrame(pos: LONGINT; VAR f : WMGraphics.Image; VAR res : LONGINT);
BEGIN {EXCLUSIVE}
ASSERT(frame # NIL);
WHILE readyBufferPool.NofBuffers() > 0 DO
vBufferPool.Add(readyBufferPool.Remove())
END;
videoDecoder.SeekFrame(pos, TRUE, res);
videoDecoder.Next; videoDecoder.Next;
videoDecoder.Render(frame);
f := frame;
videoDecoder.SeekFrame(pos, TRUE, res);
videoDecoder.Next;
IF videoDecoder.HasMoreData() THEN positionChanged := TRUE; END;
END SeekAndGetFrame;
PROCEDURE SeekFrame(pos : LONGINT; isKeyFrame : BOOLEAN; VAR res : LONGINT);
BEGIN {EXCLUSIVE}
WHILE readyBufferPool.NofBuffers() > 0 DO
vBufferPool.Add(readyBufferPool.Remove())
END;
videoDecoder.SeekFrame(pos, TRUE, res);
videoDecoder.Next;
IF videoDecoder.HasMoreData() THEN positionChanged := TRUE; END;
END SeekFrame;
PROCEDURE NofFullBuffers() : LONGINT;
BEGIN
RETURN readyBufferPool.NofBuffers()
END NofFullBuffers;
PROCEDURE Stop;
BEGIN {EXCLUSIVE}
IF Trace * TraceFiller # {} THEN KernelLog.String("MediaPlayer: Filler stopped."); KernelLog.Ln; END;
alive := FALSE;
END Stop;
PROCEDURE Close;
BEGIN {EXCLUSIVE}
IF Trace * TraceFiller # {} THEN KernelLog.String("MediaPlayer: Closing filler..."); KernelLog.Ln; END;
alive := FALSE;
IF readyBufferPool.NofBuffers() > 0 THEN vBufferPool.Add(readyBufferPool.Remove()); END;
END Close;
BEGIN {ACTIVE}
WHILE alive DO
IF videoDecoder.HasMoreData() THEN
vBuffer := vBufferPool.Remove(); (* Will block of no buffers available *)
BEGIN {EXCLUSIVE}
IF alive & videoDecoder.HasMoreData() THEN
IF PerformanceStats THEN
perf := Kernel.GetTicks();
END;
videoDecoder.Next;
videoDecoder.Render(vBuffer);
IF PerformanceStats THEN
perf := Kernel.GetTicks() - perf;
IF perf < min THEN min := perf;
ELSIF perf > max THEN max := perf;
END;
INC(tot, perf);
INC(framesDecoded);
END;
END;
END;
readyBufferPool.Add(vBuffer);
ELSE
BEGIN {EXCLUSIVE} AWAIT(positionChanged OR ~alive); positionChanged := FALSE; END;
END;
END;
IF Trace * TraceFiller # {} THEN KernelLog.String("MediaPlayer: Filler closed."); KernelLog.Ln; END;
IF PerformanceStats THEN
IF framesDecoded > 0 THEN
KernelLog.String("MediaPlayer: Decoded "); KernelLog.Int(framesDecoded, 0); KernelLog.String(" frames in "); KernelLog.Int(tot, 0);
KernelLog.String("ms (min: "); KernelLog.Int(min, 0);
KernelLog.String(", avg: "); KernelLog.Int(tot DIV framesDecoded, 0); KernelLog.String(", max: "); KernelLog.Int(max, 0); KernelLog.String(")");
KernelLog.String(", "); KernelLog.Int(dropped, 0); KernelLog.String(" frames not decoded in time"); KernelLog.Ln;
END;
END;
END Filler;
TYPE
Setup* = POINTER TO RECORD
uri- : ARRAY 256 OF CHAR;
hasAudio-, hasVideo- : BOOLEAN;
canSeek- : BOOLEAN;
maxTime- : LONGINT;
width-, height- : LONGINT;
mspf- : LONGINT;
maxFrames- : LONGINT;
channels-, bits-, rate-: LONGINT;
END;
Context = POINTER TO RECORD
uri : ARRAY 256 OF CHAR;
hasVideo, hasAudio : BOOLEAN;
canSeek : BOOLEAN;
pos, oldPos : LONGINT;
video : Codecs.VideoDecoder;
maxFrames, maxTime : LONGINT;
width, height, mspf : LONGINT;
filler : Filler;
vBuffer : VideoBuffer;
audio : Codecs.AudioDecoder;
channels, bits, rate : LONGINT;
posRate : LONGINT;
aBuffer : SoundDevices.Buffer;
channel : SoundDevices.Channel;
bufferpool : SoundDevices.BufferPool;
delay : LONGINT;
transition : WMTransitions.TransitionFade;
transitionFrame : LONGINT;
transitionDuration : LONGINT;
transitionImg : VideoBuffer;
black : VideoBuffer;
END;
TYPE
EofProc = PROCEDURE {DELEGATE} (sender, data: ANY);
EofHandler = OBJECT
VAR
proc : EofProc;
player : Player;
alive, dead, called : BOOLEAN;
PROCEDURE Call;
BEGIN {EXCLUSIVE}
called := TRUE;
END Call;
PROCEDURE Terminate;
BEGIN
IF Trace * TraceEof # {} THEN KernelLog.String("MediaPlayer: Terminating EOF handler."); KernelLog.Ln; END;
BEGIN {EXCLUSIVE} alive := FALSE; END;
BEGIN {EXCLUSIVE} AWAIT(dead); END;
END Terminate;
PROCEDURE &New*(player : Player);
BEGIN
SELF.player := player; alive := TRUE; dead := FALSE;
END New;
BEGIN {ACTIVE}
WHILE alive DO
BEGIN {EXCLUSIVE} AWAIT(~alive OR called); called := FALSE; END;
IF alive & (proc # NIL) THEN
IF Trace * TraceEof # {} THEN KernelLog.String("MediaPlayer: Call EOF procedure."); KernelLog.Ln; END;
proc(player, NIL);
END;
END;
BEGIN {EXCLUSIVE} dead := TRUE; END;
END EofHandler;
Player*= OBJECT
VAR
state : LONGINT;
current, next : Context;
nextState : LONGINT;
nextContext : Context;
requestProcessed : BOOLEAN;
lock : BOOLEAN;
console* : BOOLEAN;
soundDevice : SoundDevices.Driver;
mixerChannel, pcmChannel, mChannel : SoundDevices.MixerChannel;
channelName : ARRAY 128 OF CHAR;
pw : PlayerWindow;
timer : Kernel.Timer;
tickStart : LONGINT;
tickDelay : LONGINT;
lastUpdate : LONGINT;
videoFramesPlayed : LONGINT;
mspf : LONGINT;
setup* : PROCEDURE {DELEGATE} (data : Setup);
update* : PROCEDURE {DELEGATE} (state, pos, maxpos, displayTime: LONGINT);
eof : EofHandler;
PROCEDURE Open*(CONST uri : ARRAY OF CHAR; VAR msg : ARRAY OF CHAR; VAR res : LONGINT);
VAR context : Context;
BEGIN
context := GetContext(uri, msg, res);
IF (res # Ok) OR (context = NIL) THEN
IF Debug OR console THEN
KernelLog.String("MediaPlayer: Could not open file "); KernelLog.String(uri);
KernelLog.String("(res: "); KernelLog.Int(res, 0); KernelLog.String(", "); KernelLog.String(msg); KernelLog.String(")");
KernelLog.Ln;
END;
RETURN;
END;
RequestState(Ready, context);
IF Trace * TraceOpen # {} THEN KernelLog.String("MediaPlayer: Opened "); KernelLog.String(uri); KernelLog.Ln; END;
END Open;
PROCEDURE Play*;
BEGIN
RequestState(Playing, NIL);
END Play;
PROCEDURE DoTransition*(CONST uri: ARRAY OF CHAR; pos, duration : LONGINT; VAR msg : ARRAY OF CHAR; VAR res : LONGINT);
VAR context : Context; audioPos : LONGINT;
BEGIN
IF Trace * TraceTransitions # {} THEN
KernelLog.String("MediaPlayer: Doing a Transition to "); KernelLog.String(uri); KernelLog.String(" (Duration: ");
KernelLog.Int(duration, 0); KernelLog.String(" frames)"); KernelLog.Ln;
END;
IF (duration < 1) OR (pos < 0) THEN
IF Debug OR console THEN KernelLog.String("MediaPlayer: Warning: DoTransition: Pos or duration value adjusted."); KernelLog.Ln; END;
IF pos < 0 THEN pos := 0; END;
IF duration < 1 THEN duration := 1; END;
END;
context := GetContext(uri, msg, res);
IF context = NIL THEN
IF Debug OR console THEN KernelLog.String("MediaPlayer: Could not get context for transition: "); KernelLog.String(msg); KernelLog.Ln; END;
RETURN;
END;
IF context.hasVideo & (pos > context.maxFrames) THEN
IF Debug OR console THEN
KernelLog.String("MediaPlayer: Warning: DoTransition: Pos value clipped to maxFrames: ");
KernelLog.Int(context.maxFrames, 0); KernelLog.Ln;
END;
pos := context.maxFrames;
END;
IF context.hasVideo THEN
context.filler.SeekFrame(pos, TRUE, res);
context.pos := res; context.oldPos := res-1;
IF context.hasAudio THEN
audioPos := ENTIER(context.maxTime/10*context.posRate*(res/context.maxFrames) -
ENTIER(context.maxTime/10*context.posRate*(res/context.maxFrames)) MOD 12);
IF audioPos < 0 THEN audioPos := 0 END;
END;
NEW(context.transition); context.transition.Init(context.width, context.height);
NEW(context.transitionImg); Raster.Create(context.transitionImg, context.width, context.height, Raster.BGR565);
NEW(context.black); Raster.Create(context.black, context.width, context.height, Raster.BGR565);
context.transitionFrame := 0;
context.transitionDuration := duration;
END;
IF context.hasAudio & (soundDevice # NIL) THEN
context.audio.SeekSample(audioPos, FALSE, res);
context.channel.SetVolume(0);
context.channel.Start;
END;
IF Trace * TraceTransitions # {} THEN
KernelLog.String("MediaPlayer: Transition to pos (next keyframe): "); KernelLog.Int(context.pos, 0);
KernelLog.String(" (wanted: "); KernelLog.Int(pos, 0); KernelLog.String(")"); KernelLog.Ln;
END;
RequestState(InTransition, context);
END DoTransition;
PROCEDURE Stop*;
BEGIN
RequestState(Stopped, NIL);
END Stop;
PROCEDURE Pause*;
BEGIN
RequestState(Paused, NIL);
END Pause;
PROCEDURE GetPos*(): LONGINT;
VAR context : Context; res : LONGINT;
BEGIN {EXCLUSIVE}
res := -1;
context := current;
IF context # NIL THEN
IF context.hasVideo THEN res := 10*current.filler.GetPos() DIV (1000 DIV current.mspf);
ELSIF current.hasAudio THEN res := current.audio.GetCurrentTime();
END;
END;
RETURN res;
END GetPos;
PROCEDURE SetPos*(pos: LONGINT);
VAR current : Context; audioPos, res : LONGINT; img : WMGraphics.Image;
BEGIN {EXCLUSIVE}
IF pos < 0 THEN
IF Debug THEN KernelLog.String("MediaPlayer: Warning: Setpos to "); KernelLog.Int(pos, 0); KernelLog.String("!?!"); KernelLog.Ln; END;
pos := 0;
END;
current := SELF.current;
IF (current # NIL) & current.canSeek THEN
IF current.hasVideo THEN
IF pos > current.maxFrames THEN
IF Debug THEN KernelLog.String("MediaPlayer: Warning: Setpos to "); KernelLog.Int(pos, 0); KernelLog.String(" (>MaxFrames)!?! "); KernelLog.Ln; END;
pos := current.maxFrames;
END;
current.filler.SeekAndGetFrame(pos, img, res);
IF img # NIL THEN
IF pw # NIL THEN pw.ShowFrame(img); END;
END;
current.pos := res;
current.oldPos := current.pos-1;
IF current.hasAudio THEN
audioPos := ENTIER(current.maxTime/10*current.posRate*(res/current.maxFrames) -
ENTIER(current.maxTime/10*current.posRate*(res/current.maxFrames)) MOD 12);
IF audioPos < 0 THEN audioPos := 0 END;
current.audio.SeekSample(audioPos, FALSE, res);
END;
ELSIF current.hasAudio THEN
pos := ENTIER(pos / 10 * current.posRate);
current.audio.SeekSample(pos, FALSE, res);
pos := current.audio.GetCurrentTime();
current.pos := pos;
current.oldPos := current.pos-1;
END;
END;
END SetPos;
PROCEDURE SetEofAction(proc : EofProc);
BEGIN {EXCLUSIVE}
IF eof = NIL THEN NEW(eof, SELF); END;
eof.proc := proc;
END SetEofAction;
PROCEDURE &New*;
VAR i : LONGINT;
BEGIN
NEW(timer);
IF (SoundDevices.devices.Get("") # NIL) THEN
soundDevice := SoundDevices.GetDefaultDevice();
soundDevice.GetMixerChannel(0, mixerChannel);
mixerChannel.SetVolume(255);
FOR i := 0 TO soundDevice.GetNofMixerChannels() - 1 DO
soundDevice.GetMixerChannel(i, mChannel);
mChannel.GetName(channelName);
IF channelName = "PCMOut" THEN pcmChannel := mChannel; pcmChannel.SetVolume(255) END
END;
END;
eof := NIL;
SetState(NotReady);
END New;
PROCEDURE Acquire;
BEGIN {EXCLUSIVE}
AWAIT(lock = FALSE);
lock := TRUE;
END Acquire;
PROCEDURE Release;
BEGIN {EXCLUSIVE}
lock := FALSE;
END Release;
PROCEDURE RequestState(state : LONGINT; context : Context);
BEGIN
Acquire;
BEGIN {EXCLUSIVE}
requestProcessed := FALSE;
IF nextState # NoRequest THEN
IF nextContext # NIL THEN FreeContext(nextContext); END;
END;
nextState := state;
nextContext := context;
END;
BEGIN {EXCLUSIVE}
AWAIT(requestProcessed OR (state >= Closed));
END;
Release;
END RequestState;
PROCEDURE GetRequestedState(VAR state : LONGINT; VAR context : Context);
BEGIN {EXCLUSIVE}
state := nextState; nextState := NoRequest;
context := nextContext; nextContext := NIL;
requestProcessed := TRUE;
END GetRequestedState;
PROCEDURE SetState(state : LONGINT);
BEGIN {EXCLUSIVE}
IF Trace * TraceStates # {} THEN KernelLog.String("MediaPlayer: Set state to "); KernelLog.Int(state, 0); KernelLog.Ln; END;
SELF.state := state;
END SetState;
PROCEDURE GetState() : LONGINT;
BEGIN {EXCLUSIVE}
RETURN state;
END GetState;
PROCEDURE ToggleFullScreen*(sender, data : ANY);
BEGIN {EXCLUSIVE}
IF (pw # NIL) & (~ForceFullscreen) THEN pw.ToggleFullscreen; END;
END ToggleFullScreen;
PROCEDURE CheckWindow(context : Context);
VAR oldPw : PlayerWindow;
BEGIN
IF context.hasVideo THEN
IF (pw # NIL) & pw.fullscreen THEN
ELSIF (pw = NIL) OR (pw.GetWidth() # context.width) OR (pw.GetHeight() # context.height) THEN
oldPw := pw;
NEW(pw, context.width, context.height, FALSE, SELF, TRUE);
IF ForceFullscreen THEN pw.ToggleFullscreen; END;
IF oldPw # NIL THEN oldPw.Close; END;
END;
ELSIF pw # NIL THEN
pw.Close;
END;
END CheckWindow;
PROCEDURE InitTime;
BEGIN
tickStart := Kernel.GetTicks(); tickDelay := 0;
videoFramesPlayed := 0;
END InitTime;
PROCEDURE GetContext(CONST uri : ARRAY OF CHAR; VAR msg : ARRAY OF CHAR; VAR res : LONGINT) : Context;
VAR
in, audioStream, videoStream : Streams.Reader;
demux : Codecs.AVDemultiplexer;
audioDecoder : Codecs.AudioDecoder; videoDecoder : Codecs.VideoDecoder;
streamInfo : Codecs.AVStreamInfo;
nofStreams, type, maxFrames, maxTime, width, height, mspf : LONGINT;
channels, rate, bits: LONGINT;
buffer : SoundDevices.Buffer;
hasAudio, hasVideo : BOOLEAN;
audioCanSeek, videoCanSeek : BOOLEAN;
name, ext : ARRAY 256 OF CHAR;
context : Context;
i : LONGINT;
BEGIN
IF Trace * TraceOpen # {} THEN KernelLog.String("MediaPlayer: Get decoders for: "); KernelLog.String(uri); KernelLog.Ln; END;
in := Codecs.OpenInputStream(uri);
IF (in = NIL) THEN
res := CouldNotOpenStream; COPY("Can't open stream: ", msg); Strings.Append(msg, uri);
IF Debug OR console THEN KernelLog.String("MediaPlayer: "); KernelLog.String(msg); KernelLog.Ln; END;
RETURN NIL;
END;
Strings.GetExtension(uri, name, ext);
Strings.UpperCase(ext);
demux := Codecs.GetDemultiplexer(ext);
IF demux = NIL THEN
IF Trace * TraceOpen # {} THEN KernelLog.String("MediaPlayer: No Demux found: "); KernelLog.String(ext); KernelLog.Ln; END;
audioDecoder := Codecs.GetAudioDecoder(ext);
IF (audioDecoder # NIL) THEN
audioDecoder.Open(in, res);
IF (res # Codecs.ResOk) THEN
res := AudioNotCompatible; COPY("Audio stream not compatible: ", msg); Strings.Append(msg, uri);
IF Debug OR console THEN KernelLog.String("MediaPlayer: "); KernelLog.String(msg); KernelLog.Ln; END;
RETURN NIL;
END;
hasAudio := TRUE;
audioCanSeek := audioDecoder.CanSeek();
IF in IS Codecs.FileInputStream THEN
audioDecoder.SetStreamLength(in(Codecs.FileInputStream).f.Length());
END;
ELSE
videoDecoder := Codecs.GetVideoDecoder(ext);
IF (videoDecoder # NIL) THEN
videoDecoder.Open(in, res);
IF (res # Codecs.ResOk) THEN
res := VideoNotCompatible; COPY("Video stream not compatible: ", msg); Strings.Append(msg, uri);
IF Debug OR console THEN KernelLog.String("MediaPlayer: "); KernelLog.String(msg); KernelLog.Ln; END;
RETURN NIL;
END;
hasVideo := TRUE;
videoCanSeek := videoDecoder.CanSeek();
ELSE
res := NoDecoders; COPY("No decoder available for: ", msg); Strings.Append(msg, uri);
IF Debug OR console THEN KernelLog.String("MediaPlayer: "); KernelLog.String(msg); KernelLog.Ln; END;
RETURN NIL;
END;
END;
ELSE
IF Trace * TraceOpen # {} THEN KernelLog.String("MediaPlayer: Demux found: "); KernelLog.String(ext); KernelLog.Ln; END;
demux.Open(in, res);
IF (res # Codecs.ResOk) THEN
res := DemuxNotCompatible; COPY("Demux stream not compatible: ", msg); Strings.Append(msg, uri);
IF Debug OR console THEN KernelLog.String("MediaPlayer: "); KernelLog.String(msg); KernelLog.Ln; END;
RETURN NIL;
END;
nofStreams := demux.GetNumberOfStreams();
IF Trace * TraceOpen # {} THEN KernelLog.String("MediaPlayer: Number of Streams: "); KernelLog.Int(nofStreams, 0); KernelLog.Ln; END;
FOR i := 0 TO nofStreams-1 DO
IF Trace * TraceOpen # {} THEN KernelLog.String("MediaPlayer: Processing Stream: "); KernelLog.Int(i, 0); END;
type := demux.GetStreamType(i);
IF Trace * TraceOpen # {} THEN KernelLog.String(" with Type: "); KernelLog.Int(type, 0); KernelLog.Ln; END;
IF (type = Codecs.STVideo) THEN
videoStream := demux.GetStream(i);
videoStream(Codecs.DemuxStream).Open(demux, i);
streamInfo := demux.GetStreamInfo(i);
videoDecoder := Codecs.GetVideoDecoder(streamInfo.contentType);
IF (videoDecoder = NIL) THEN
res := NoVideoDecoder; COPY("No Decoder for video format: ", msg); Strings.Append(msg, streamInfo.contentType);
IF Debug OR console THEN KernelLog.String("MediaPlayer: "); KernelLog.String(msg); KernelLog.Ln; END;
hasVideo := FALSE;
ELSE
videoDecoder.Open(videoStream, res);
IF (res # Codecs.ResOk) THEN
res := VideoNotCompatible; COPY("Video stream not compatible: ", msg); Strings.Append(msg, uri);
IF Debug OR console THEN KernelLog.String("MediaPlayer: "); KernelLog.String(msg); KernelLog.Ln; END;
hasVideo := FALSE;
ELSE
videoCanSeek := videoDecoder.CanSeek();
maxFrames := streamInfo.frames;
IF streamInfo.rate # 0 THEN maxTime := 10 * streamInfo.frames DIV streamInfo.rate;
ELSE videoCanSeek := FALSE;
END;
hasVideo := TRUE;
END;
END;
ELSIF (type = Codecs.STAudio) THEN
audioStream := demux.GetStream(i);
audioStream(Codecs.DemuxStream).Open(demux, i);
streamInfo := demux.GetStreamInfo(i);
audioDecoder := Codecs.GetAudioDecoder(streamInfo.contentType);
IF (audioDecoder = NIL) THEN
res := NoAudioDecoder; COPY("No Decoder for audio format: ", msg); Strings.Append(msg, streamInfo.contentType);
IF Debug OR console THEN KernelLog.String("MediaPlayer: "); KernelLog.String(msg); KernelLog.Ln; END;
hasAudio := FALSE;
ELSE
audioDecoder.Open(audioStream, res);
IF (res # Codecs.ResOk) THEN
res := AudioNotCompatible; COPY("Audio stream not compatible: ", msg); Strings.Append(msg, uri);
IF Debug OR console THEN KernelLog.String("MediaPlayer: "); KernelLog.String(msg); KernelLog.Ln; END;
hasAudio := FALSE;
ELSE
audioCanSeek := audioDecoder.CanSeek();
hasAudio := TRUE;
END;
END;
ELSE
IF Trace * TraceOpen # {} THEN KernelLog.String("MediaPlayer: Unknown Stream Type: "); KernelLog.Int(type, 0); KernelLog.Ln; END;
END;
END;
END;
IF hasVideo OR hasAudio THEN
NEW(context);
COPY(uri, context.uri);
IF hasVideo THEN
context.hasVideo := TRUE;
context.video := videoDecoder;
context.maxFrames := maxFrames; context.maxTime := maxTime;
videoDecoder.GetVideoInfo(width, height, mspf);
context.width := width; context.height := height; context.mspf := mspf;
NEW(context.filler, width, height, videoDecoder);
END;
IF hasAudio & (soundDevice # NIL)THEN
context.hasAudio := TRUE;
context.audio := audioDecoder;
IF (channels # 0) & (bits # 0) & (rate # 0) THEN
context.delay := ENTIER(AudioBuffers * AudioBufferSize * 8 / (channels * bits * rate) ) + 1 ;
ELSE
context.delay := 50;
END;
context.delay := context.delay + AudioConstantDelay;
IF Trace * TraceOpen # {} THEN KernelLog.String("Audio delay: "); KernelLog.Int(context.delay, 0); KernelLog.String("ms"); KernelLog.Ln; END;
NEW(context.bufferpool, AudioBuffers);
FOR i := 0 TO AudioBuffers-1 DO
NEW(buffer); buffer.len := AudioBufferSize; NEW(buffer.data, AudioBufferSize);
context.bufferpool.Add(buffer);
END;
audioDecoder.GetAudioInfo(channels, rate, bits);
context.channels := channels; context.rate := rate; context.bits := bits;
IF (audioStream # NIL) & (audioStream IS Codecs.DemuxStream) THEN
context.posRate := audioStream(Codecs.DemuxStream).streamInfo.rate;
END;
IF ~hasVideo THEN
context.maxTime := audioDecoder.GetTotalSamples() DIV rate * 10;
context.maxFrames := maxTime;
END;
audioDecoder.SeekSample(0, FALSE, res);
soundDevice.OpenPlayChannel(context.channel, rate, bits, channels, SoundDevices.FormatPCM, res);
IF context.channel = NIL THEN
IF Debug OR console THEN KernelLog.String("MediaPlayer: Could not open audio play channel."); KernelLog.Ln; END;
ELSE
context.channel.RegisterBufferListener(context.bufferpool.Add);
context.channel.SetVolume(255);
END;
END;
context.canSeek := (~hasVideo OR videoCanSeek) & (~hasAudio OR audioCanSeek);
ELSE
res := NoDecoders; COPY("No demux/decoder found for ", msg); Strings.Append(msg, uri);
IF Debug OR console THEN KernelLog.String("MediaPlayer: "); KernelLog.String(msg); KernelLog.Ln; END;
END;
IF Trace * TraceOpen # {} THEN
KernelLog.String("Context opened: Maxtime: "); KernelLog.Int(context.maxTime, 0);
KernelLog.String(", maxFrames: "); KernelLog.Int(context.maxFrames, 0); KernelLog.Ln;
END;
RETURN context;
END GetContext;
PROCEDURE FreeContext(context : Context);
BEGIN
IF context # NIL THEN
IF context.filler # NIL THEN context.filler.Close; context.filler := NIL; END;
IF context.channel # NIL THEN context.channel.Close; context.channel := NIL; END;
END;
END FreeContext;
PROCEDURE Loop(sender, data: ANY);
BEGIN
IF Trace * TraceEof # {} THEN KernelLog.String("MediaPlayer: EOF Loop."); KernelLog.Ln; END;
Stop; Play;
END Loop;
PROCEDURE Quit(sender, data: ANY);
BEGIN
IF Trace * TraceEof # {} THEN KernelLog.String("MediaPlayer: EOF Quit."); KernelLog.Ln; END;
Close;
END Quit;
PROCEDURE RenderAudio(c : Context);
BEGIN
ASSERT(c # NIL);
WHILE c.hasAudio & c.audio.HasMoreData() & (c.bufferpool.NofBuffers() > 0) DO
c.aBuffer := c.bufferpool.Remove();
c.audio.FillBuffer(c.aBuffer);
c.channel.QueueBuffer(c.aBuffer);
IF ~c.hasVideo THEN
c.pos := c.audio.GetCurrentTime();
END
END;
END RenderAudio;
PROCEDURE RenderVideo(c : Context);
BEGIN
ASSERT((c # NIL) & (c.hasVideo));
c.vBuffer := c.filler.GetNextBuffer();
IF pw # NIL THEN pw.ShowFrame(c.vBuffer) END;
c.filler.ReturnBuffer(c.vBuffer);
INC(videoFramesPlayed); c.oldPos := c.pos; INC(c.pos);
END RenderVideo;
PROCEDURE RenderVideoTransition(from, to : Context);
VAR temp : VideoBuffer;
BEGIN
ASSERT((to # NIL) & (to.transition # NIL) & (to.transitionImg # NIL) & (to.black # NIL));
INC(to.transitionFrame);
IF Trace * TraceTransitions # {} THEN
KernelLog.String("MediaPlayer: Transition Frame ");
KernelLog.Int(to.transitionFrame, 0); KernelLog.String(" of "); KernelLog.Int(to.transitionDuration, 0);
KernelLog.Ln;
END;
IF (from # NIL) THEN
IF from.filler.NofFullBuffers() >= (to.transitionDuration - to.transitionFrame) THEN from.filler.Stop; END;
from.vBuffer := from.filler.GetNextBuffer();
temp := from.vBuffer;
ELSE
temp := to.black;
END;
to.vBuffer := to.filler.GetNextBuffer();
to.transition.CalcImage(temp, to.vBuffer, to.transitionImg, to.transitionFrame*255 DIV to.transitionDuration);
IF pw # NIL THEN pw.ShowFrame(to.transitionImg) END;
to.filler.ReturnBuffer(to.vBuffer);
to.oldPos := to.pos; INC(to.pos);
IF from # NIL THEN
from.filler.ReturnBuffer(from.vBuffer);
from.oldPos := from.pos; INC(from.pos);
END;
INC(videoFramesPlayed);
IF to.transitionFrame >= to.transitionDuration THEN
IF (from # NIL) THEN FreeContext(from); from := NIL; END;
IF Trace * TraceTransitions # {} THEN KernelLog.String("MediaPlayer: Transition Finished."); KernelLog.Ln; END;
END;
END RenderVideoTransition;
PROCEDURE Render(c1, c2 : Context);
VAR
tickIs: LONGINT;
tickShould : LONGINT;
BEGIN
IF c1 # NIL THEN
IF GetState() = InTransition THEN
IF c2 # NIL THEN
IF c2.hasAudio THEN
RenderAudio(c2);
c2.channel.SetVolume(256 - c1.transitionFrame*(256 DIV c1.transitionDuration));
ELSE
timer.Sleep(40);
END;
END;
IF c1.hasAudio THEN
RenderAudio(c1);
c1.channel.SetVolume(256*c1.transitionFrame DIV c1.transitionDuration);
END;
IF c1.hasVideo THEN RenderVideoTransition(c2, c1); END;
ELSE
IF c1.hasAudio THEN
RenderAudio(c1);
END;
IF c1.hasVideo THEN
RenderVideo(c1);
END;
END;
END;
tickIs := Kernel.GetTicks() - tickStart;
tickShould := videoFramesPlayed * mspf;
IF tickIs < tickShould THEN
timer.Sleep(tickShould-tickIs);
END;
IF Trace * TraceRendering # {} THEN
KernelLog.String("Frame: "); KernelLog.Int(videoFramesPlayed, 0);
KernelLog.String(" [Is: "); KernelLog.Int(tickIs, 0); KernelLog.String(", Should="); KernelLog.Int(tickShould, 0);
KernelLog.String(",Sleep: ");
IF (tickIs < tickShould) THEN
IF tickDelay < (tickShould-tickIs) THEN
KernelLog.Int(tickShould - tickIs - tickDelay, 0);
ELSE
KernelLog.Int(8, 0);
END;
ELSE KernelLog.Int(0, 0);
END;
KernelLog.String(", Delay: "); KernelLog.Int(tickDelay, 0);
KernelLog.String("]"); KernelLog.Ln;
END;
END Render;
PROCEDURE Close*;
BEGIN
RequestState(Closed, NIL);
BEGIN {EXCLUSIVE} AWAIT(state = Closed); END;
mplayer := NIL;
IF Trace * TracePlayer # {} THEN KernelLog.String("MediaPlayer closed."); KernelLog.Ln; END;
END Close;
PROCEDURE StopIntern;
VAR img : WMGraphics.Image; res : LONGINT;
BEGIN
FreeContext(next); next := NIL;
IF current # NIL THEN
IF current.hasVideo THEN current.filler.SeekAndGetFrame(0, img, res); END;
IF current.hasAudio THEN current.channel.Stop; current.audio.SeekSample(0, FALSE, res); END;
current.pos := 0; current.oldPos := -1;
END;
IF pw # NIL THEN
IF img # NIL THEN pw.ShowFrame(img); ELSE pw.ShowBlack; END;
END;
END StopIntern;
PROCEDURE StartPlayIntern;
VAR res : LONGINT;
BEGIN
IF current # NIL THEN
IF state = Finished THEN
current.pos := 0; current.oldPos := -1;
IF current.hasVideo THEN current.filler.SeekFrame(0, TRUE, res); END;
IF current.hasAudio THEN current.channel.Stop; current.audio.SeekSample(0, FALSE, res); END;
END;
IF current.hasAudio THEN current.channel.SetVolume(255); current.channel.Start; END;
InitTime;
END;
END StartPlayIntern;
PROCEDURE PauseIntern;
BEGIN
IF (current # NIL) & current.hasAudio THEN current.channel.Pause; END;
IF (next # NIL) & next.hasAudio THEN next.channel.Pause; END;
END PauseIntern;
PROCEDURE ResumeIntern;
VAR audioPos, res : LONGINT;
BEGIN
IF (current # NIL) THEN
IF current.hasVideo THEN
res := (current.video.GetCurrentFrame()-current.filler.NofFullBuffers());
IF current.hasAudio THEN
audioPos := ENTIER(current.maxTime/10*current.posRate*(res/current.maxFrames) -
ENTIER(current.maxTime/10*current.posRate*(res/current.maxFrames)) MOD 12);
IF audioPos < 0 THEN audioPos := 0 END;
current.audio.SeekSample(audioPos, FALSE, res);
END;
END;
IF current.hasAudio THEN current.channel.Start; END;
END;
IF (next # NIL) THEN
IF next.hasVideo THEN
res := (next.video.GetCurrentFrame()-next.filler.NofFullBuffers());
IF next.hasAudio THEN
audioPos := ENTIER(next.maxTime/10*next.posRate*(res/next.maxFrames) -
ENTIER(next.maxTime/10*next.posRate*(res/next.maxFrames)) MOD 12);
IF audioPos < 0 THEN audioPos := 0 END;
next.audio.SeekSample(audioPos, FALSE, res);
END;
END;
IF next.hasAudio THEN next.channel.Start; END;
END;
IF (current # NIL) OR (next # NIL) THEN
InitTime;
END;
END ResumeIntern;
PROCEDURE OpenIntern(nextContext : Context);
VAR img : WMGraphics.Image; data : Setup; res : LONGINT;
BEGIN
FreeContext(next); next := NIL;
FreeContext(current); current := nextContext;
mspf := current.mspf; current.pos := 0; current.oldPos := -1;
CheckWindow(current);
IF current.hasVideo THEN
current.filler.SeekAndGetFrame(0, img, res);
END;
IF pw # NIL THEN
IF img # NIL THEN pw.ShowFrame(img); ELSE pw.ShowBlack; END;
END;
IF setup # NIL THEN
NEW(data);
data.hasVideo := current.hasVideo;
data.hasAudio := current.hasAudio;
data.canSeek := current.canSeek;
COPY(nextContext.uri, data.uri);
data.mspf := current.mspf;
data.maxFrames := current.maxFrames;
data.maxTime := current.maxTime;
IF current.hasVideo THEN
data.width := current.width; data.height := current.height;
END;
IF current.hasAudio THEN
data.channels := current.channels; data.bits := current.bits; data.rate := current.rate;
END;
setup(data);
END;
END OpenIntern;
PROCEDURE IsValidStateTransition(from, to : LONGINT) : BOOLEAN;
VAR res : BOOLEAN;
BEGIN
res := FALSE;
CASE from OF
|NotReady: IF (to = Ready) OR (to = InTransition) THEN res := TRUE; END;
|Ready: IF (to = Ready) OR (to = Playing) OR (to = InTransition) THEN res := TRUE; END;
|Playing: IF (to = Ready) OR (to = Paused) OR (to = Stopped) OR (to = InTransition) THEN res := TRUE; END;
|Paused: IF (to = Ready) OR (to = Playing) OR (to = Stopped) OR (to = Paused) OR (to = InTransition) THEN res := TRUE; END;
|Stopped: IF (to = Ready) OR (to = Playing) OR (to = InTransition) THEN res := TRUE; END;
|Finished: IF (to = Ready) OR (to = Playing) OR (to = Stopped) OR (to = InTransition) THEN res := TRUE; END;
|InTransition: IF (to = Ready) OR (to = Paused) OR (to = Stopped) OR (to = InTransition) THEN res := TRUE; END;
|Error:
|Closed:
ELSE
IF Debug THEN KernelLog.String("MediaPlayer: Start state of state transition not known."); KernelLog.Ln; END;
END;
IF (to = Error) OR (to = Closed) THEN res := TRUE; END;
RETURN res;
END IsValidStateTransition;
PROCEDURE EvaluateState;
VAR
oldState : LONGINT;
isValid : BOOLEAN; nextState : LONGINT; nextContext : Context;
audioFinished, videoFinished : BOOLEAN;
currentTime : LONGINT; callUpdate : BOOLEAN;
temp : LONGINT;
BEGIN
GetRequestedState(nextState, nextContext); oldState := state;
IF (nextState # NoRequest) THEN
isValid := IsValidStateTransition(state, nextState);
IF isValid THEN
CASE nextState OF
NoRequest:
|Ready:
OpenIntern(nextContext);
SetState(Ready);
|Playing:
StartPlayIntern; SetState(Playing);
|Paused:
IF state = Paused THEN
ResumeIntern; SetState(Playing);
ELSE
PauseIntern; SetState(Paused);
END;
|Stopped:
StopIntern; SetState(Stopped);
|InTransition:
FreeContext(next); next := current;
current := nextContext;
mspf := current.mspf;
CheckWindow(current);
InitTime;
SetState(InTransition);
|Closed:
FreeContext(current); current := NIL;
FreeContext(next); next := NIL;
FreeContext(nextContext); nextContext := NIL;
SetState(Closed);
ELSE
IF Debug THEN KernelLog.String("MediaPlayer: Warning: Ignore request to set state to: "); KernelLog.Int(nextState, 0); KernelLog.Ln; END;
END;
END;
IF Trace * TraceStates # {} THEN
KernelLog.String("MediaPlayer: Request state transition from '");
KernelLog.Int(oldState, 0); KernelLog.String("' to '"); KernelLog.Int(nextState, 0); KernelLog.String("': ");
IF isValid THEN KernelLog.String("Valid (New state: "); KernelLog.Int(state, 0); KernelLog.String(")");
ELSE KernelLog.String("Invalid (Rejected)");
END;
KernelLog.Ln;
END;
END;
IF nextState = NoRequest THEN
IF (state = InTransition) & (current.transitionFrame >= current.transitionDuration) THEN
SetState(Playing);
END;
audioFinished := FALSE; videoFinished := FALSE;
IF current = NIL THEN
audioFinished := TRUE; videoFinished := TRUE;
ELSE
IF ~current.hasVideo OR (current.hasVideo & ~current.video.HasMoreData() & (current.filler.NofFullBuffers() <= 0)) THEN videoFinished := TRUE; END;
IF ~current.hasAudio OR (current.hasAudio & ~current.audio.HasMoreData()) THEN audioFinished := TRUE; END;
IF Debug THEN
END;
END;
IF (current = NIL) OR ((current # NIL) & current.hasVideo & videoFinished) OR ((current # NIL) & (~current.hasVideo) & audioFinished) THEN
IF state # Finished THEN
IF Trace * TracePlayer # {} THEN
KernelLog.String("MediaPlayer: Finished playing: ");
IF (current = NIL) THEN KernelLog.String("No context.");
ELSE
IF videoFinished THEN KernelLog.String("[Video finished]"); END;
IF audioFinished THEN KernelLog.String("[Audio finished]"); END;
END;
KernelLog.Ln;
END;
IF eof # NIL THEN eof.Call; END;
END;
SetState(Finished);
END
END;
IF current # NIL THEN
IF current.hasVideo THEN
currentTime := 10*current.pos DIV (1000 DIV current.mspf);
ELSE
currentTime := current.audio.GetCurrentTime();
END;
temp := Kernel.GetTicks();
callUpdate := (update # NIL) & ((state = Finished) OR (nextState # NoRequest) OR (temp - lastUpdate >= UpdateInterval));
IF callUpdate THEN lastUpdate := temp; update(state, current.pos, current.maxFrames, currentTime); END;
ELSE
IF update # NIL THEN update(state, 0, 0, 0); END;
END;
END EvaluateState;
BEGIN {ACTIVE}
WHILE state < Closed DO
(* Synchronization to player commands *)
BEGIN {EXCLUSIVE} AWAIT((state = Playing) OR (state = InTransition) OR (state >= Closed) OR (nextState # NoRequest)); END;
IF state < Closed THEN
(* Within this IF statement we have exlusive access to all Context objects *)
(* Render next video/audio frame *)
IF nextState = NoRequest THEN Render(current, next); END;
(* State management (process state change requests and current state *)
EvaluateState;
END;
END;
FINALLY
FreeContext(current); current := NIL;
FreeContext(next); next := NIL;
FreeContext(nextContext); nextContext := NIL;
IF eof # NIL THEN eof.Terminate; END;
IF pw # NIL THEN pw.Close; pw := NIL; END;
SetState(Closed);
END Player;
VAR mplayer : Player;
PROCEDURE Open*(context : Commands.Context);
VAR filename, msg : ARRAY 256 OF CHAR; res : LONGINT;
BEGIN {EXCLUSIVE}
context.arg.String(filename);
IF mplayer = NIL THEN NEW(mplayer); END;
mplayer.Open(filename, msg, res);
IF res = Streams.Ok THEN
mplayer.Play;
ELSE
context.error.String("MediaPlayer: Could not open file: "); context.error.String(filename);
context.error.String(" (res: "); context.error.Int(res, 0); context.error.String(", ");
context.error.String(msg); context.error.String(")"); context.error.Ln;
END;
END Open;
PROCEDURE TransitionTo*(context : Commands.Context);
VAR filename, msg : ARRAY 256 OF CHAR; duration : LONGINT; res : LONGINT;
BEGIN {EXCLUSIVE}
context.arg.SkipWhitespace; context.arg.String(filename);
context.arg.SkipWhitespace; context.arg.Int(duration, FALSE);
IF (context.arg.res # Streams.Ok) OR (duration < 1) THEN duration := 25; END;
IF mplayer # NIL THEN
mplayer.DoTransition(filename, 0, duration, msg, res);
IF res # Ok THEN
context.error.String("MediaPlayer.DoTransition Error (res: "); context.error.Int(res, 0);
context.error.String(", "); context.error.String(msg); context.error.String(")"); context.error.Ln;
END;
ELSE
NEW(mplayer); mplayer.DoTransition(filename, 0, duration, msg, res);
IF res # Ok THEN
context.error.String("MediaPlayer.DoTransition Error (res: "); context.error.Int(res, 0);
context.error.String(", "); context.error.String(msg); context.error.String(")"); context.error.Ln;
END;
END;
END TransitionTo;
PROCEDURE Close*;
BEGIN
Cleanup;
END Close;
PROCEDURE SetEofAction*(context : Commands.Context);
VAR command : ARRAY 32 OF CHAR;
BEGIN
IF mplayer # NIL THEN
context.arg.SkipWhitespace; context.arg.String(command);
IF Strings.Match("none", command) THEN
mplayer.eof := NIL;
context.out.String("MediaPlayer: Set EOF to NIL.");
ELSIF Strings.Match("loop", command) THEN
mplayer.SetEofAction(mplayer.Loop);
context.out.String("MediaPlayer: Set EOF to Loop.");
ELSIF Strings.Match("quit", command) THEN
mplayer.SetEofAction(mplayer.Quit);
context.out.String("MediaPlayer: Set EOF to Quit.");
ELSE
context.out.String("MediaPlayer: Command not recognized.");
END;
ELSE
context.error.String("MediaPlayer: Cannot set EOF - player not running."); context.error.Ln;
END;
context.out.Ln;
END SetEofAction;
PROCEDURE Cleanup;
BEGIN {EXCLUSIVE}
IF mplayer # NIL THEN mplayer.Close; mplayer := NIL; END;
END Cleanup;
BEGIN
Modules.InstallTermHandler(Cleanup);
END MediaPlayer.
------------------------------------------------------------
i810Sound.Install ~
SystemTools.Free MediaPlayer ~
SystemTools.Free WMPlayer MediaPlayer DivXDecoder DivXHelper DivXTypes AVI~
PC.Compile AVI.Mod DivXTypes.Mod DivXHelper.Mod DivXDecoder.Mod MediaPlayer.Mod WMPlayer.Mod~
SystemTools.Free WMPlayer MediaPlayer ~
WMPlayer.Open flags.avi~
MediaPlayer.Open flags.avi~
MediaPlayer.TransitionTo flags.avi 25 ~
MediaPlayer.TransitionTo flags.avi 300 ~
MediaPlayer.Close ~
MediaPlayer.SetEofAction none ~
MediaPlayer.SetEofAction loop ~
MediaPlayer.SetEofAction quit ~