/***************************************************************************** * * This program is free software ; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * $Id: player.c 623 2006-02-01 13:19:15Z picard $ * * The Core Pocket Media Player * Copyright (c) 2004-2005 Gabor Kovacs * ****************************************************************************/ #include "common.h" #include "playlist/m3u.h" #include "../config.h" #define VIDEO_STREAMING_UNDERRUN (256*1024)/BLOCKSIZE #define ALIGN_SIZE 4096 #define PROBE_SIZE ALIGN_SIZE #define MAXSKIP 16 typedef struct ref { node* Node; int RefCount; } ref; typedef struct item { tchar_t URL[MAXPATH]; tchar_t Title[256]; tick_t Length; bool_t Changed; } item; typedef struct player_base { player Player; bool_t BufferWarning; int BufferSize2; //in blocksize int MDBufferSize2; //in blocksize int CurrBufferSize2; //in blocksize bool_t SoftVolume; bool_t Background; bool_t Foreground; bool_t PowerOff; bool_t PowerOffReSync; bool_t FlowBackground; bool_t MicroDrive; bool_t Repeat; bool_t Shuffle; bool_t PlayAtOpen; bool_t PlayAtOpenFull; bool_t ExitAtEnd; bool_t KeepPlayVideo; bool_t KeepPlayAudio; bool_t ShowInBackground; bool_t SingleClickFullScreen; bool_t KeepList; tick_t MoveBack; tick_t MoveFFwd; int BurstStart; // in blocksize int UnderRun; // percent int AudioUnderRun; //kbyte fraction FullZoom; fraction SkinZoom; fraction Aspect; int FullDir; int SkinDir; int RelDir; bool_t SmoothZoom50; bool_t SmoothZoomAlways; bool_t VideoAccel; bool_t AutoPreRotate; int SkipAccelFrom; int AudioQuality; int VideoQuality; int AOutputId; int VOutputId; int AOutputIdMax; int VOutputIdMax; fraction SeekAfterSync; bool_t SeekAfterSyncPrevKey; fraction InSeekPos; fraction PlaySpeed; fraction FFwdSpeed; int Volume; int Pan; int PreAmp; bool_t Mute; bool_t TimerLeft; node* Color; node* Equalizer; bool_t Eq; // states notify ListNotify; notify Notify; rect SkinViewport; void* Wnd; bool_t DiscardList; bool_t FullScreen; bool_t Clipping; bool_t Rotating; rect Viewport; bool_t Primary; bool_t Overlay; bool_t HasHost; bool_t Streaming; // HasHost and filesize==-1 bool_t Timing; bool_t TemporaryHidden; // hidden till next sync bool_t FullScreenAfterSync; int Stereo; int StreamNo; // current media bool_t PreRotate; int BufferMax; stream* Input; stream* InputBase; node* Timer; node* AOutput; node* VOutput; node* VIDCT; node* FlowBuffer; format* Format; int Selected[PACKET_MAX]; // stream numbers // threads and timings bool_t NoMoreInput; bool_t Play; bool_t FFwd; bool_t Sync; bool_t Fill; bool_t LoadMode; bool_t Bench; bool_t SaveMicroDrive; bool_t WaitForProcess; bool_t TimerPlay; bool_t InSeek; bool_t UpdateStreamsNeeded; tick_t Position; tick_t PosNotify; fraction Speed; tick_t PosNotifyStep; tick_t BenchTime; point BenchSrc; point BenchDst; int BenchStart; int IOError; int UsedEnough2; //in blocksize int MinBuffer; //in blocksize uint32_t RndSeed; bool_t RunInput; bool_t RunProcess; #ifdef MULTITHREAD int ProcessPriority; int InputPriority; int LockInputCount; // just hint to sleep in input thread so other other thread can lock it int LockProcessCount; // just hint to sleep in process thread so other other thread can lock it void* Lock; // parameter read/write void* LockComment; void* LockInput; void* LockProcess; void* EventProcess; // ProcessThread has processed some packets (InputThread can continue) void* EventRunInput; void* EventRunProcess; void* ProcessThread; void* InputThread; int ProcessLocked; int InputLocked; #endif // playlist int Current; bool_t CurrentChanged; int PlayListCount; item* PlayList; int* PlayIndex; // paint rgbval_t ColorKey; // colorkeybursh color void* ColorKeyBrush; void* BlackBrush; array Ref; // ref array Comment; // [Stream,Name,0,Value,0] bool_t StreamSkip[MAXSTREAM]; tchar_t Title[256]; tchar_t CurrentDir[MAXPATH]; int CodecSkipCount; int CodecSkip[MAXSKIP]; } player_base; static const datatable PlayerParams[] = { { PLAYER_AUTOPREROTATE, TYPE_BOOL, DF_SETUP }, { PLAYER_REPEAT, TYPE_BOOL, DF_SETUP|DF_HIDDEN }, { PLAYER_SHUFFLE, TYPE_BOOL, DF_SETUP|DF_HIDDEN }, { PLAYER_KEEPPLAY_AUDIO,TYPE_BOOL, DF_SETUP }, { PLAYER_KEEPPLAY_VIDEO,TYPE_BOOL, DF_SETUP }, { PLAYER_SHOWINBACKGROUND,TYPE_BOOL, DF_SETUP }, { PLAYER_SINGLECLICKFULLSCREEN, TYPE_BOOL, DF_SETUP }, { PLAYER_KEEPLIST, TYPE_BOOL, DF_SETUP }, { PLAYER_PLAYATOPEN, TYPE_BOOL, DF_SETUP }, { PLAYER_PLAYATOPEN_FULL,TYPE_BOOL, DF_SETUP }, { PLAYER_EXIT_AT_END, TYPE_BOOL, DF_SETUP }, { PLAYER_FULL_ZOOM, TYPE_FRACTION, DF_SETUP|DF_PERCENT|DF_HIDDEN }, { PLAYER_SKIN_ZOOM, TYPE_FRACTION, DF_SETUP|DF_PERCENT|DF_HIDDEN }, { PLAYER_PLAY_SPEED, TYPE_FRACTION, DF_SETUP|DF_PERCENT|DF_GAP }, { PLAYER_FFWD_SPEED, TYPE_FRACTION, DF_SETUP|DF_PERCENT }, { PLAYER_MOVEFFWD_STEP, TYPE_TICK, DF_SETUP|DF_MINMAX, 0, 600*TICKSPERSEC }, { PLAYER_MOVEBACK_STEP, TYPE_TICK, DF_SETUP|DF_MINMAX, 0, 600*TICKSPERSEC }, { PLAYER_BUFFER_SIZE, TYPE_INT, DF_SETUP|DF_KBYTE|DF_GAP|DF_HIDDEN, 512, 128*1024 }, { PLAYER_UNDERRUN, TYPE_INT, DF_SETUP|DF_PERCENT|DF_HIDDEN,0,PERCENT_ONE }, { PLAYER_AUDIO_UNDERRUN,TYPE_INT, DF_SETUP|DF_KBYTE|DF_HIDDEN,32,128*1024 }, { PLAYER_MICRODRIVE, TYPE_BOOL, DF_SETUP|DF_GAP|DF_HIDDEN }, { PLAYER_MD_BUFFER_SIZE,TYPE_INT, DF_SETUP|DF_KBYTE|DF_HIDDEN, 512, 128*1024 }, { PLAYER_BURSTSTART, TYPE_INT, DF_SETUP|DF_KBYTE|DF_HIDDEN, 512, 128*1024 }, { PLAYER_AOUTPUTID, TYPE_INT, DF_SETUP|DF_HIDDEN }, { PLAYER_AOUTPUTID_MAX, TYPE_INT, DF_SETUP|DF_HIDDEN }, // should be after PLAYER_AOUTPUTID { PLAYER_VOUTPUTID, TYPE_INT, DF_SETUP|DF_HIDDEN }, { PLAYER_VOUTPUTID_MAX, TYPE_INT, DF_SETUP|DF_HIDDEN }, // should be after PLAYER_VOUTPUTID { PLAYER_VIDEO_ACCEL, TYPE_BOOL, DF_SETUP|DF_HIDDEN }, { PLAYER_LIST_COUNT, TYPE_INT, DF_SETUP|DF_HIDDEN }, { PLAYER_LIST_CURRENT, TYPE_INT, DF_SETUP|DF_HIDDEN }, { PLAYER_LIST_CURRIDX, TYPE_INT, DF_HIDDEN }, { PLAYER_SMOOTH50, TYPE_BOOL, DF_SETUP|DF_HIDDEN }, { PLAYER_SMOOTHALWAYS, TYPE_BOOL, DF_SETUP|DF_HIDDEN }, { PLAYER_STEREO, TYPE_INT, DF_SETUP|DF_HIDDEN }, { PLAYER_ASPECT, TYPE_FRACTION, DF_SETUP|DF_HIDDEN }, { PLAYER_FULL_DIR, TYPE_INT, DF_SETUP|DF_HIDDEN }, { PLAYER_SKIN_DIR, TYPE_INT, DF_SETUP|DF_HIDDEN }, { PLAYER_AUDIO_QUALITY, TYPE_INT, DF_SETUP|DF_ENUMSTRING|DF_HIDDEN, PLAYERQUALITY_ENUM }, { PLAYER_VIDEO_QUALITY, TYPE_INT, DF_SETUP|DF_ENUMSTRING|DF_HIDDEN, PLAYERQUALITY_ENUM }, { PLAYER_ASTREAM, TYPE_INT, DF_HIDDEN|DF_SETUP }, { PLAYER_VSTREAM, TYPE_INT, DF_HIDDEN|DF_SETUP }, { PLAYER_SUBSTREAM, TYPE_INT, DF_HIDDEN|DF_SETUP }, { PLAYER_PERCENT, TYPE_FRACTION, DF_HIDDEN|DF_SETUP }, { PLAYER_VOLUME, TYPE_INT, DF_MINMAX|DF_HIDDEN|DF_SETUP, 0, 100 }, { PLAYER_PAN, TYPE_INT, DF_MINMAX|DF_HIDDEN|DF_SETUP, -100, 100 }, { PLAYER_PREAMP, TYPE_INT, DF_MINMAX|DF_HIDDEN|DF_SETUP, -100, 100 }, { PLAYER_MUTE, TYPE_BOOL, DF_HIDDEN|DF_SETUP }, { PLAYER_TIMER_LEFT, TYPE_BOOL, DF_HIDDEN|DF_SETUP }, { PLAYER_BACKGROUND, TYPE_BOOL, DF_HIDDEN }, { PLAYER_FOREGROUND, TYPE_BOOL, DF_HIDDEN }, { PLAYER_PLAY, TYPE_BOOL, DF_HIDDEN }, { PLAYER_FFWD, TYPE_BOOL, DF_HIDDEN }, { PLAYER_POSITION, TYPE_TICK, DF_HIDDEN }, { PLAYER_DURATION, TYPE_TICK, DF_HIDDEN }, { PLAYER_TIMER, TYPE_STRING, DF_HIDDEN }, { PLAYER_TITLE, TYPE_STRING }, { PLAYER_INPUT, TYPE_NODE, DF_HIDDEN, STREAM_CLASS }, { PLAYER_FORMAT, TYPE_NODE, DF_HIDDEN, FORMAT_CLASS }, { PLAYER_AOUTPUT, TYPE_NODE, DF_HIDDEN, AOUT_CLASS }, { PLAYER_VOUTPUT, TYPE_NODE, DF_HIDDEN, VOUT_CLASS }, { PLAYER_SKIN_VIEWPORT, TYPE_RECT, DF_HIDDEN }, { PLAYER_CLIPPING, TYPE_BOOL, DF_HIDDEN }, { PLAYER_FULLSCREEN, TYPE_BOOL, DF_HIDDEN }, { PLAYER_LOADMODE, TYPE_BOOL, DF_HIDDEN | DF_RDONLY }, { PLAYER_CURRENTDIR, TYPE_STRING, DF_SETUP|DF_HIDDEN }, DATATABLE_END(PLAYER_ID) }; static const datatable BufferParams[] = { { PLAYER_BUFFER_SIZE, TYPE_INT, DF_SETUP|DF_KBYTE|DF_GAP, 512, 128*1024 }, { PLAYER_UNDERRUN, TYPE_INT, DF_SETUP|DF_PERCENT,0,PERCENT_ONE }, { PLAYER_AUDIO_UNDERRUN,TYPE_INT, DF_SETUP|DF_KBYTE,32,128*1024 }, { PLAYER_MICRODRIVE, TYPE_BOOL, DF_SETUP|DF_GAP }, { PLAYER_MD_BUFFER_SIZE,TYPE_INT, DF_SETUP|DF_KBYTE, 512, 128*1024 }, { PLAYER_BURSTSTART, TYPE_INT, DF_SETUP|DF_KBYTE, 512, 128*1024 }, DATATABLE_END(PLAYER_BUFFER_ID) }; static int Enum(player_base* p, int* No, datadef* Param) { if (NodeEnumTable(No,Param,PlayerParams) == ERR_NONE) return ERR_NONE; if (*NoPlayListCount*3) { memset(Param,0,sizeof(datadef)); if (*No >= 2*p->PlayListCount) { Param->No = PLAYER_LIST_LENGTH + (*No - 2*p->PlayListCount); Param->Type = TYPE_TICK; Param->Size = sizeof(tick_t); } else { if (*No >= p->PlayListCount) Param->No = PLAYER_LIST_TITLE + (*No - p->PlayListCount); else Param->No = PLAYER_LIST_URL + *No; Param->Type = TYPE_STRING; Param->Size = MAXDATA; } Param->Flags = DF_HIDDEN | DF_SETUP; Param->Class = PLAYER_ID; return ERR_NONE; } *No -= p->PlayListCount*3; return ERR_INVALID_PARAM; } static NOINLINE int UpdateSpeed(player_base* p) { if (p->Bench) { p->Speed.Num = 0; p->Speed.Den = 1; } else if (p->FFwd) p->Speed = p->FFwdSpeed; else p->Speed = p->PlaySpeed; p->PosNotifyStep = Scale(TICKSPERSEC,p->Speed.Num,p->Speed.Den); if (p->Timer) p->Timer->Set(p->Timer,TIMER_SPEED,&p->Speed,sizeof(p->Speed)); return ERR_NONE; } static NOINLINE void NotifyList(player_base* p) { if (p->ListNotify.Func) p->ListNotify.Func(p->ListNotify.This,0,0); } static NOINLINE void Notify(player_base* p,int Id,int Value) { if (p->Notify.Func) p->Notify.Func(p->Notify.This,Id,Value); } static NOINLINE void UpdateListLength(player_base* p,tick_t Length) { if (p->Current < p->PlayListCount && !p->PlayList[p->Current].Changed && p->PlayList[p->Current].Length != Length) { p->PlayList[p->Current].Length = Length; NotifyList(p); } } #define INVALID_COMMENT -2 static INLINE int CommentStream(const uint8_t* p) { return *(tchar_t*)p; } static INLINE const tchar_t* CommentName(const uint8_t* p) { return (const tchar_t*)(p+sizeof(tchar_t)); } static INLINE const tchar_t* CommentValue(const uint8_t* p) { const tchar_t* i = CommentName(p); return i+tcslen(i)+1; } static const uint8_t* CommentNext(const uint8_t* p) { const tchar_t* i = CommentValue(p); return (const uint8_t*)(i+tcslen(i)+1); } static const tchar_t* GetFirstComment(player_base* p, int Id) { const uint8_t *i; const tchar_t* Name = PlayerComment(Id); for (i=ARRAYBEGIN(p->Comment,uint8_t);i!=ARRAYEND(p->Comment,uint8_t);i=CommentNext(i)) if (CommentStream(i)!=INVALID_COMMENT && tcsicmp(CommentName(i),Name)==0) return CommentValue(i); return T(""); } static void UpdateListTitle(player_base* p) { const tchar_t* Title; const tchar_t* Artist; LockEnter(p->LockComment); Title = GetFirstComment(p,COMMENT_TITLE); Artist = GetFirstComment(p,COMMENT_ARTIST); if (!Artist[0]) Artist = GetFirstComment(p,COMMENT_AUTHOR); if (Artist[0] && Title[0]) stprintf_s(p->Title,TSIZEOF(p->Title),T("%s - %s"),Artist,Title); else { if (!Title[0]) Title = Artist; tcscpy_s(p->Title,TSIZEOF(p->Title),Title); } LockLeave(p->LockComment); Notify(p,PLAYER_TITLE,0); if (p->Current < p->PlayListCount && !p->PlayList[p->Current].Changed) { tcscpy_s(p->PlayList[p->Current].Title,TSIZEOF(p->PlayList[p->Current].Title),p->Title); NotifyList(p); } } static NOINLINE bool_t IsCoverArt(player_base* p) { int CoverArt = -2; if (p->Format) p->Format->Get(p->Format,FORMAT_COVERART,&CoverArt,sizeof(CoverArt)); return CoverArt==p->Selected[PACKET_VIDEO]; } static NOINLINE bool_t IsVideo(player_base* p) { return p->VOutput && !IsCoverArt(p); } static NOINLINE void UpdateTimeouts(player_base* p) { bool_t KeepProcess = (p->Play || p->FFwd) && p->Format; if (!KeepProcess || !p->Sync) // VOutput not neccessary updated till first sync is over SleepTimeout(KeepProcess,p->Bench || (p->Primary && IsVideo(p))); } static NOINLINE void UpdateRunInput(player_base* p) { // we don't want deadlocks so LoadMode will restart InputThread even with NoMoreInput p->RunInput = p->Format && p->Input && (!p->NoMoreInput || p->LoadMode); #ifdef MULTITHREAD if (p->RunInput) EventSet(p->EventRunInput); else EventReset(p->EventRunInput); #endif } static NOINLINE void UpdateRunProcess(player_base* p) { p->RunProcess = p->Format && p->Input && p->Timer && (((p->FFwd || p->Play) && !p->LoadMode) || p->Sync || p->Fill); #ifdef MULTITHREAD if (p->RunProcess) EventSet(p->EventRunProcess); else EventReset(p->EventRunProcess); #endif } static NOINLINE void UpdatePriority(player_base* p) { #ifdef MULTITHREAD if (p->InputThread && p->ProcessThread) { int Priority; Priority = (!p->TimerPlay || !QueryAdvanced(ADVANCED_PRIORITY))?0:1; if (p->ProcessPriority != Priority) { p->ProcessPriority = Priority; ThreadPriority(p->ProcessThread,Priority); } Priority = (((p->Play || p->FFwd) && !p->Sync) || p->LoadMode)?0:1; if (p->InputPriority != Priority) { p->InputPriority = Priority; ThreadPriority(p->InputThread,Priority); } } #endif } static NOINLINE void UpdatePlay(player_base* p) { p->TimerPlay = (p->Play || p->FFwd) && !p->InSeek && !p->Sync && !p->Fill && !p->LoadMode; UpdatePriority(p); p->Timer->Set(p->Timer,TIMER_PLAY,&p->TimerPlay,sizeof(bool_t)); if (p->VOutput) p->VOutput->Set(p->VOutput,VOUT_PLAY,&p->TimerPlay,sizeof(bool_t)); } static int CmpRef(const ref* a, const ref* b) { if (a->Node > b->Node) return 1; if (a->Node < b->Node) return -1; return 0; } static void Unlock(player_base* p, bool_t Input); static void Lock(player_base* p, bool_t Input); static NOINLINE bool_t Used(player_base* p,node* Node) { const ref *i; for (i=ARRAYBEGIN(p->Ref,ref);i!=ARRAYEND(p->Ref,ref);++i) if (i->Node == Node) return 1; return 0; } static NOINLINE void Flush(player_base* p) { const ref *i; node* Skip = NULL; if (IsCoverArt(p)) Skip = p->FlowBuffer; for (i=ARRAYBEGIN(p->Ref,ref);i!=ARRAYEND(p->Ref,ref);++i) if (i->Node != Skip) i->Node->Set(i->Node,FLOW_FLUSH,NULL,0); if (p->FlowBuffer && p->FlowBuffer != Skip) p->FlowBuffer->Set(p->FlowBuffer,FLOW_FLUSH,NULL,0); } static NOINLINE int UpdateBackground(player_base* p) { bool_t b; #ifdef MULTITHREAD assert(p->ProcessLocked != ThreadId() && p->InputLocked != ThreadId()); #endif LockEnter(p->Lock); b = p->Background && !p->ShowInBackground && !p->Play && !p->FFwd; #if !defined(TARGET_WINCE) if (p->PowerOff) b = 1; #endif if (b != p->FlowBackground) { bool_t Locked = 0; const ref *i; p->FlowBackground = b; for (i=ARRAYBEGIN(p->Ref,ref);i!=ARRAYEND(p->Ref,ref);++i) if (i->Node->Get(i->Node,FLOW_BACKGROUND,&b,sizeof(b))==ERR_NONE) { if (!Locked) { Lock(p,1); Locked = 1; } i->Node->Set(i->Node,FLOW_BACKGROUND,&p->FlowBackground,sizeof(p->FlowBackground)); } if (Locked) Unlock(p,1); } LockLeave(p->Lock); return ERR_NONE; } static void ReSync(player_base* p,bool_t Seek); static int UpdateFormat(player_base* p,bool_t BufferChanged); static int SetPlay(player_base* p,bool_t Stop); static int SetPlayListCount(player_base* p, int Count); static void Paint(player_base* p,void* DC,int x0,int y0); static int UpdateVideoEx(player_base* p,node* VOutput,int ForceRefresh,bool_t Active) { int Caps; bool_t Dither; packetformat Mode; blitfx FX; bool_t FullScreen; bool_t Visible = (p->Foreground || p->ShowInBackground) && !p->Rotating && Active; bool_t Refresh; bool_t Updating; bool_t VOutVisible; bool_t Primary; bool_t Overlay; rect Viewport; int RelDir; if (VOutput) { VOutput->Get(VOutput,VOUT_PRIMARY,&Primary,sizeof(bool_t)); Viewport = p->SkinViewport; FullScreen = p->FullScreen || !Primary; if (FullScreen && VOutput->Get(VOutput,OUT_OUTPUT|PIN_FORMAT,&Mode,sizeof(Mode))==ERR_NONE && Mode.Type == PACKET_VIDEO) PhyToVirt(NULL,&Viewport,&Mode.Format.Video); FX.Flags = 0; if (p->SmoothZoom50) FX.Flags |= BLITFX_ARITHSTRETCH50; if (p->SmoothZoomAlways) FX.Flags |= BLITFX_ARITHSTRETCHALWAYS; if (QueryAdvanced(ADVANCED_SLOW_VIDEO)) FX.Flags |= BLITFX_ONLYDIFF; if (QueryAdvanced(ADVANCED_COLOR_LOOKUP)) FX.Flags |= BLITFX_COLOR_LOOKUP; p->Color->Get(p->Color,COLOR_DITHER,&Dither,sizeof(Dither)); if (Dither) FX.Flags |= BLITFX_DITHER; p->Color->Get(p->Color,COLOR_BRIGHTNESS,&FX.Brightness,sizeof(FX.Brightness)); p->Color->Get(p->Color,COLOR_CONTRAST,&FX.Contrast,sizeof(FX.Contrast)); p->Color->Get(p->Color,COLOR_SATURATION,&FX.Saturation,sizeof(FX.Saturation)); p->Color->Get(p->Color,COLOR_R_ADJUST,&FX.RGBAdjust[0],sizeof(FX.RGBAdjust[0])); p->Color->Get(p->Color,COLOR_G_ADJUST,&FX.RGBAdjust[1],sizeof(FX.RGBAdjust[1])); p->Color->Get(p->Color,COLOR_B_ADJUST,&FX.RGBAdjust[2],sizeof(FX.RGBAdjust[2])); if (FullScreen) { FX.Direction = p->FullDir; FX.ScaleY = FX.ScaleX = Scale(SCALE_ONE,p->FullZoom.Num,p->FullZoom.Den); } else { FX.Direction = p->SkinDir; FX.ScaleY = FX.ScaleX = Scale(SCALE_ONE,p->SkinZoom.Num,p->SkinZoom.Den); } if (FX.Direction < 0) FX.Direction = 0; else if (Primary) FX.Direction = CombineDir(GetOrientation(),0,FX.Direction); RelDir = FX.Direction; VOutVisible = Visible && !p->TemporaryHidden; Updating = 1; VOutput->Set(VOutput,VOUT_UPDATING,&Updating,sizeof(bool_t)); VOutput->Set(VOutput,VOUT_FULLSCREEN,&FullScreen,sizeof(bool_t)); VOutput->Set(VOutput,VOUT_VIEWPORT,&Viewport,sizeof(rect)); VOutput->Set(VOutput,VOUT_FX,&FX,sizeof(blitfx)); VOutput->Set(VOutput,VOUT_ASPECT,&p->Aspect,sizeof(fraction)); VOutput->Set(VOutput,VOUT_CLIPPING,&p->Clipping,sizeof(p->Clipping)); VOutput->Set(VOutput,VOUT_VISIBLE,&VOutVisible,sizeof(VOutVisible)); VOutput->Set(VOutput,VOUT_AUTOPREROTATE,&p->AutoPreRotate,sizeof(bool_t)); Updating = 0; VOutput->Set(VOutput,VOUT_UPDATING,&Updating,sizeof(bool_t)); VOutput->Get(VOutput,VOUT_OVERLAY,&Overlay,sizeof(bool_t)); } else { Primary = 1; Overlay = 0; RelDir = 0; Viewport = p->SkinViewport; } if (Active) { p->RelDir = RelDir; p->Primary = Primary; p->Overlay = Overlay; p->Viewport = Viewport; #ifdef TARGET_PALMOS if (!p->Clipping && Visible) Paint(p,p->Wnd,0,0); #endif // try to redraw overlay Refresh = VOutput && ForceRefresh>=0 && !p->Sync; if (Refresh && p->FlowBuffer && !ForceRefresh) Refresh = p->FlowBuffer->Set(p->FlowBuffer,FLOW_RESEND,NULL,0) != ERR_NONE; if (Refresh && p->Format) ReSync(p,0); // redraw the hard way: resync if (!p->Wnd || !VOutput || VOutput->Get(VOutput,VOUT_CAPS,&Caps,sizeof(Caps))!=ERR_NONE) Caps = -1; p->Color->Set(p->Color,COLOR_CAPS,&Caps,sizeof(Caps)); } return ERR_NONE; } static NOINLINE int UpdateVideo(player_base* p,int ForceRefresh) { return UpdateVideoEx(p,p->VOutput,ForceRefresh,1); } static NOINLINE int UpdateBuffer(player_base* p) { node* Node = p->AOutput; if (Node) { bool_t b = IsVideo(p); Node->Set(Node,AOUT_MODE,&b,sizeof(b)); } p->MinBuffer = 0; if (p->Format) p->Format->Get(p->Format,FORMAT_MIN_BUFFER,&p->MinBuffer,sizeof(int)); return ERR_NONE; } static NOINLINE player_base* RefreshAudio(player_base* p) { node* Node = p->AOutput; if (Node && QueryAdvanced(ADVANCED_SYSTEMVOLUME)) { Node->Get(Node,AOUT_VOLUME,&p->Volume,sizeof(int)); Node->Get(Node,AOUT_MUTE,&p->Mute,sizeof(bool_t)); Node->Get(Node,AOUT_PAN,&p->Pan,sizeof(int)); } return p; } static NOINLINE int UpdateAudioEx(player_base* p,node* Node) { if (Node) { Node->Set(Node,AOUT_STEREO,&p->Stereo,sizeof(int)); Node->Set(Node,AOUT_QUALITY,&p->AudioQuality,sizeof(int)); Node->Set(Node,AOUT_PREAMP,&p->PreAmp,sizeof(int)); Node->Set(Node,AOUT_VOLUME,&p->Volume,sizeof(int)); Node->Set(Node,AOUT_MUTE,&p->Mute,sizeof(bool_t)); Node->Set(Node,AOUT_PAN,&p->Pan,sizeof(int)); } return ERR_NONE; } static NOINLINE int UpdateAudio(player_base* p) { return UpdateAudioEx(p,p->AOutput); } static NOINLINE int UpdateVolume(player_base* p) { if (p->AOutput) p->AOutput->Set(p->AOutput,AOUT_VOLUME,&p->Volume,sizeof(int)); return ERR_NONE; } static int UpdateStreams(player_base* p,int ForceRefresh,bool_t CodecSkip); static NOINLINE int UpdatePowerOff(player_base* p) { Notify(p,PLAYER_POWEROFF,p->PowerOff); // before poweroff is handled! (PalmOS may need to turn on clipping) #ifdef TARGET_WINCE if (p->PowerOff) { bool_t b; Lock(p,0); if (p->VOutput && p->VOutput->Get(p->VOutput,FLOW_BACKGROUND,&b,sizeof(b))==ERR_NONE) { int Old = p->VOutputId; bool_t OldAccel = p->VideoAccel; p->VOutputId = 0; UpdateStreams(p,-1,0); p->VOutputId = Old; p->VideoAccel = OldAccel; p->PowerOffReSync = 1; } if (p->Play || p->FFwd) { Lock(p,1); p->Play = 0; p->FFwd = 0; p->Fill = 1; SetPlay(p,0); Unlock(p,1); } Unlock(p,0); } else { if (p->PowerOffReSync) { p->PowerOffReSync = 0; UpdateStreams(p,-1,0); ReSync(p,1); } } #else if (p->PowerOff && (p->Play || p->FFwd || p->Fill)) { Lock(p,1); p->Play = 0; p->FFwd = 0; p->Fill = 0; // do not fill, because acceleration devices could be turned off SetPlay(p,0); // calls UpdateBackground Unlock(p,1); } else if (!p->PowerOff && !p->Play && !p->FFwd && !p->Fill) { Lock(p,1); p->Fill = 1; // turn on fill SetPlay(p,0); // calls UpdateBackground Unlock(p,1); } else UpdateBackground(p); #endif return ERR_NONE; } static NOINLINE int UpdateForeground(player_base* p) { if (!p->Foreground) if ((IsVideo(p) && !p->KeepPlayVideo) || (!IsVideo(p) && !p->KeepPlayAudio)) { Lock(p,1); SetPlay(p,1); Unlock(p,1); } UpdateVideo(p,0); return ERR_NONE; } static int FindCurrentIndex(player_base* p) { int Index; for (Index=0;IndexPlayListCount;++Index) if (p->PlayIndex[Index] == p->Current) return Index; return 0; } static uint32_t Rand(player_base* p) { p->RndSeed = p->RndSeed*0x8088405U + 0x251001U; return p->RndSeed; } static int RandomizePlayIndex(player_base* p,bool_t CurrentAsFirst) { int No,n; int* Index = p->PlayIndex; for (No=0;NoPlayListCount;++No) Index[No] = No; if (p->Shuffle) { for (n=0;n<4;++n) for (No=0;NoPlayListCount;++No) SwapInt(&Index[No],&Index[Rand(p) % p->PlayListCount]); if (CurrentAsFirst) { // make sure current media is the first // so the full range of playlist will be played before it's regenerated No = FindCurrentIndex(p); if (No>0) SwapInt(&Index[No],&Index[0]); } } return ERR_NONE; } static int SetPlayListCount(player_base* p, int Count) { int i,j; int* Index = p->PlayIndex; item* List = (item*)realloc(p->PlayList,sizeof(item)*Count); if (!List && Count) return ERR_OUT_OF_MEMORY; if (p->PlayListCount > Count) { // remove unwanted indexes for (i=0,j=0;iPlayListCount;++i) if (Index[i]PlayListCount;iShuffle) SwapInt(&Index[i],&Index[Rand(p) % (i+1)]); } p->PlayIndex = Index; p->PlayList = List; p->PlayListCount = Count; if (p->Current >= Count) { p->Current = 0; p->CurrentChanged = 1; } NotifyList(p); return ERR_NONE; } static void StopLoadMode(player_base* p) { if (p->LoadMode) { DEBUG_MSG(DEBUG_TEST,T("LoadMode Leaving")); p->LoadMode = 0; UpdatePlay(p); // UpdateInputPriority(p) called too UpdateRunProcess(p); UpdateRunInput(p); Notify(p,PLAYER_LOADMODE,p->LoadMode); } } static void StopBench(player_base* p) { p->BenchTime = Scale(GetTimeTick() - p->BenchStart,TICKSPERSEC,GetTimeFreq()); p->Bench = 0; p->MicroDrive = p->SaveMicroDrive; p->BenchSrc.x = p->BenchSrc.y = 0; p->BenchDst.x = p->BenchDst.y = 0; if (p->VOutput) { rect Rect; packetformat Format; if (p->VOutput->Get(p->VOutput,OUT_INPUT|PIN_FORMAT,&Format,sizeof(Format))==ERR_NONE) { p->BenchSrc.x = Format.Format.Video.Width; p->BenchSrc.y = Format.Format.Video.Height; } if (p->VOutput->Get(p->VOutput,VOUT_OUTPUTRECT,&Rect,sizeof(Rect))==ERR_NONE) { p->BenchDst.x = Rect.Width; p->BenchDst.y = Rect.Height; } } UpdateFormat(p,0); UpdatePlay(p); UpdateSpeed(p); Flush(p); Notify(p,PLAYER_BENCHMARK,p->BenchTime); } static void UpdateKeepAlive(player_base* p) { bool_t KeepAlive = p->FFwd || p->Play; if (p->VOutput) p->VOutput->Set(p->VOutput,OUT_KEEPALIVE,&KeepAlive,sizeof(KeepAlive)); if (p->AOutput) p->AOutput->Set(p->AOutput,OUT_KEEPALIVE,&KeepAlive,sizeof(KeepAlive)); } static int SetPlay(player_base* p,bool_t Stop) { bool_t Refresh; if (Stop) { p->Play = 0; p->FFwd = 0; p->Fill = 0; p->Sync = 0; p->InSeek = 0; if (p->TemporaryHidden) { p->TemporaryHidden = 0; UpdateVideo(p,-1); } } DEBUG_MSG2(DEBUG_PLAYER,T("Play %d,%d"),p->Play,p->FFwd); StopLoadMode(p); if (p->Bench && !p->Play) StopBench(p); if (p->Format && p->Input && p->Timer) { if (p->NoMoreInput && (p->Play || p->FFwd) && p->IOError) { p->Fill = 1; p->NoMoreInput = 0; p->IOError = 0; UpdateRunInput(p); } if (p->TimerPlay) { UpdateKeepAlive(p); UpdatePlay(p); UpdateSpeed(p); } else { UpdateSpeed(p); UpdatePlay(p); UpdateKeepAlive(p); } UpdateRunProcess(p); // make sure current frame is show after pausing a dropping period Refresh = p->VOutput && !p->Play && !p->FFwd && !p->Sync && p->Fill; if (Refresh && p->FlowBuffer) Refresh = p->FlowBuffer->Set(p->FlowBuffer,FLOW_RESEND,NULL,0) != ERR_NONE; if (Refresh) ReSync(p,0); } else UpdateKeepAlive(p); UpdateTimeouts(p); UpdateBackground(p); Notify(p,PLAYER_PLAY,p->Play); return ERR_NONE; } static int Seek2(player_base* p,tick_t Time,const fraction* Percent,bool_t PrevKey) { tick_t Duration; int Result = ERR_INVALID_DATA; if (p->Format && p->Input && p->Timer) { int FileSize; int FilePos = -1; if (Percent) { if (p->Format->Get(p->Format,FORMAT_DURATION,&Duration,sizeof(Duration))==ERR_NONE) { Time = Scale(Duration,Percent->Num,Percent->Den); if (Time < 0) Time = 0; } else if (p->Input->Get(p->Input,STREAM_LENGTH,&FileSize,sizeof(FileSize))==ERR_NONE) { Time = -1; FilePos = Scale(FileSize,Percent->Num,Percent->Den); if (FilePos < 0) FilePos = 0; } else return ERR_NOT_SUPPORTED; } p->SeekAfterSync.Num = -1; DEBUG_MSG2(DEBUG_PLAYER,T("Sync time:%d filepos:%d"),Time,FilePos); Result = p->Format->Sync(p->Format,Time,FilePos,PrevKey); if (Result == ERR_LOADING_HEADER && p->Sync && Percent) { p->SeekAfterSync = *Percent; p->SeekAfterSyncPrevKey = PrevKey; Result = ERR_NONE; } else if (Result == ERR_NONE) { Flush(p); p->NoMoreInput = 0; p->BufferMax = p->CurrBufferSize2; p->Position = Time; p->Sync = 1; p->PosNotify = -1; StopLoadMode(p); UpdatePlay(p); UpdateRunProcess(p); UpdateRunInput(p); // wakeup input threads (if needed) #ifdef MULTITHREAD EventSet(p->EventProcess); #else p->WaitForProcess = 0; #endif } Notify(p,PLAYER_PERCENT,0); } else if (!p->Wnd && Percent) { p->SeekAfterSync = *Percent; p->SeekAfterSyncPrevKey = 1; Result = ERR_NONE; } return Result; } static int InSeek(player_base* p,tick_t Time,const fraction* Percent,bool_t PrevKey) { int Result; #ifdef MULTITHREAD assert(p->ProcessLocked != ThreadId() && p->InputLocked != ThreadId()); #endif LockEnter(p->Lock); if (p->InSeek) { tick_t Duration; fraction Pos; if (Percent) Pos = *Percent; else if (p->Format->Get(p->Format,FORMAT_DURATION,&Duration,sizeof(Duration))==ERR_NONE) { Pos.Num = Time; Pos.Den = Duration; } else { Pos.Num = -1; Pos.Den = 0; } if (EqFrac(&p->InSeekPos,&Pos)) { LockLeave(p->Lock); return ERR_NONE; } p->InSeekPos = Pos; if (p->InSeekPos.Num>=0 && (p->Sync || !IsVideo(p))) { p->SeekAfterSync = p->InSeekPos; p->SeekAfterSyncPrevKey = PrevKey; Notify(p,PLAYER_PERCENT,0); LockLeave(p->Lock); return ERR_NONE; } } Lock(p,1); Result = Seek2(p,Time,Percent,PrevKey); Unlock(p,1); LockLeave(p->Lock); return Result; } static void ReSync(player_base* p,bool_t DoSeek) { if (p->Format) { if (DoSeek && p->Position>=0) Seek2(p,p->Position,NULL,1); else if (p->Format->Sync(p->Format,-1,-1,0) == ERR_NONE) { p->Sync = 1; p->PosNotify = -1; StopLoadMode(p); UpdatePlay(p); UpdateRunProcess(p); // wakeup input threads, just in case #ifdef MULTITHREAD EventSet(p->EventProcess); #else p->WaitForProcess = 0; #endif } } } static bool_t SupportVQNode(node* Node) { int Shift; bool_t Buffered; if (NodeIsClass(Node->Class,IDCT_CLASS)) { if (Node->Get(Node,IDCT_SHIFT,&Shift,sizeof(Shift))!=ERR_NONE) return 0; } else if (Node->Get(Node,FLOW_BUFFERED,&Buffered,sizeof(Buffered))==ERR_NONE && Buffered) return 0; return 1; } static bool_t SupportVQ(player_base* p) { const ref *i; for (i=ARRAYBEGIN(p->Ref,ref);i!=ARRAYEND(p->Ref,ref);++i) if (!SupportVQNode(i->Node)) return 0; if (p->FlowBuffer && !SupportVQNode(p->FlowBuffer)) // flowbuffer may not be in ref array (accel. idct) return 0; return 1; } static int UpdateVideoQuality(player_base* p,node* Node) { if (NodeIsClass(Node->Class,IDCT_CLASS)) { int Shift = 2-p->VideoQuality; Node->Set(Node,IDCT_SHIFT,&Shift,sizeof(Shift)); } return ERR_NONE; } static void ClearStats(player_base* p) { int Zero=0; if (p->VOutput) { p->VOutput->Set(p->VOutput,OUT_TOTAL,&Zero,sizeof(Zero)); p->VOutput->Set(p->VOutput,OUT_DROPPED,&Zero,sizeof(Zero)); } if (p->AOutput) { p->AOutput->Set(p->AOutput,OUT_TOTAL,&Zero,sizeof(Zero)); p->AOutput->Set(p->AOutput,OUT_DROPPED,&Zero,sizeof(Zero)); } } static void StartBench(player_base* p) { int Used; p->SaveMicroDrive = p->MicroDrive; p->MicroDrive = 0; p->InSeek = 0; UpdateFormat(p,0); if (!QueryAdvanced(ADVANCED_BENCHFROMPOS)) Seek2(p,0,NULL,0); p->BufferMax = p->CurrBufferSize2; while (p->Format->Read(p->Format,p->BufferMax,&Used) == ERR_NONE && Used < p->UsedEnough2); ClearStats(p); p->Bench = 1; p->BenchStart = GetTimeTick(); p->Play = 1; p->FFwd = 0; p->Fill = 0; SetPlay(p,0); } static void SetTimerNode(player_base* p,node* Timer,bool_t SaveTime) { tick_t Time = 0; if (p->Timer && SaveTime) p->Timer->Get(p->Timer,TIMER_TIME,&Time,sizeof(Time)); p->Timer = Timer; if (p->Timer) { p->Timer->Set(p->Timer,TIMER_SPEED,&p->Speed,sizeof(p->Speed)); p->Timer->Set(p->Timer,TIMER_TIME,&Time,sizeof(Time)); UpdatePlay(p); } UpdateRunProcess(p); } static node* AddRef(player_base* p,node* Node); static void ReleaseRef(player_base* p,node* Node); static bool_t UpdateNode(player_base* p,node* Node); static void ReleaseNode(player_base* p,node* Node); static bool_t UpdateOutput(player_base* p,node* From,const datadef* FromDef,int* FoundFirst); static NOINLINE void NullPin(node* p,int No) { pin Pin; Pin.Node = NULL; Pin.No = 0; p->Set(p,No,&Pin,sizeof(Pin)); } static void TryComment(player_base* p,node* From,const datadef* FromDef) { pin Pin; Pin.Node = (node*)p; Pin.No = PLAYER_COMMENT+p->StreamNo; From->Set(From,FromDef->No,&Pin,sizeof(Pin)); } static void ReleaseOutput(player_base* p,node* Node,datadef* DataDef) { node* Null = NULL; pin Pin; node* Ptr; bool_t RdOnly = (DataDef->Flags & DF_RDONLY)!=0; switch (DataDef->Type) { case TYPE_NODE: if (Node->Get(Node,DataDef->No,&Ptr,sizeof(Ptr))==ERR_NONE && Ptr) { ReleaseNode(p,Ptr); if (!RdOnly) { Node->Set(Node,DataDef->No,&Null,sizeof(Null)); ReleaseRef(p,Ptr); } } break; case TYPE_COMMENT: if (!RdOnly) NullPin(Node,DataDef->No); break; case TYPE_PACKET: if (Node->Get(Node,DataDef->No,&Pin,sizeof(Pin))==ERR_NONE && Pin.Node) { ReleaseNode(p,Pin.Node); if (!RdOnly) { Disconnect(Node,DataDef->No,Pin.Node,Pin.No); ReleaseRef(p,Pin.Node); } } } } static void ReleaseNode(player_base* p,node* Node) { if (Node) { bool_t False = 0; datadef DataDef; int No; if (p->VOutput == Node) { p->VOutput = NULL; p->VIDCT = NULL; } if (p->AOutput == Node) { SetTimerNode(p,NodeEnumObject(NULL,SYSTIMER_ID),1); // restore system timer p->AOutput = NULL; } if (p->FlowBuffer == Node) p->FlowBuffer = NULL; Node->Set(Node,FLOW_BACKGROUND,&False,sizeof(bool_t)); for (No=0;NodeEnum(Node,No,&DataDef)==ERR_NONE;++No) if (DataDef.Flags & DF_OUTPUT) ReleaseOutput(p,Node,&DataDef); } } static node* GetNode(player_base* p,int Class) { node* Node = NULL; bool_t ReUse = 0; bool_t IDCT = 0; if (Class == (int)VOUT_IDCT_CLASS(p->VOutputId) && NodeIsClass(Class,IDCT_CLASS)) { Class = p->VOutputId; IDCT = 1; } if (Class == p->AOutputId || Class == p->VOutputId) { ref *i; for (i=ARRAYBEGIN(p->Ref,ref);i!=ARRAYEND(p->Ref,ref);++i) if (i->Node->Class == Class) { Node = i->Node; ReUse = 1; break; } } if (!Node) { Node = NodeCreate(Class); if (Node) { if (NodeIsClass(Class,VOUT_CLASS)) UpdateVideoEx(p,Node,-1,0); if (NodeIsClass(Class,AOUT_CLASS)) UpdateAudioEx(p,Node); UpdateVideoQuality(p,Node); } } if (Node) { pin Pin; if (IDCT) { node* i; Node->Get(Node,VOUT_IDCT,&i,sizeof(i)); if (!i) { if (!ReUse) NodeDelete(Node); return NULL; } Node = i; } Pin.No = PLAYER_NOT_SUPPORTED_DATA; Pin.Node = (node*)p; Node->Set(Node,FLOW_NOT_SUPPORTED,&Pin,sizeof(Pin)); AddRef(p,Node); } return Node; } static bool_t TryPin(player_base* p,node* From,int FromNo,int ToClass,int* FoundFirst,int Failed) { node* To; if (ToClass == Failed) return 0; To = GetNode(p,ToClass); if (To) { packetformat Format; datadef ToDef; int No; int Result; for (No=0;NodeEnum(To,No,&ToDef)==ERR_NONE;++No) if (ToDef.Type == TYPE_PACKET && (ToDef.Flags & DF_INPUT) && !(ToDef.Flags & DF_RDONLY)) { if (To->Get(To,ToDef.No|PIN_FORMAT,&Format,sizeof(Format))==ERR_NONE && Format.Type != PACKET_NONE) continue; // already connected Result = ConnectionUpdate(From,FromNo,To,ToDef.No); if (Result == ERR_NOT_SUPPORTED && FoundFirst) *FoundFirst = 2; // found codec, but this type is not supported else if (Result == ERR_NONE) { if (FoundFirst) *FoundFirst = 1; if (UpdateNode(p,To)) return 1; } Disconnect(From,FromNo,To,ToDef.No); } ReleaseRef(p,To); } return 0; } static bool_t IsCodecSkip(player_base* p,int Id) { int i; for (i=0;iCodecSkipCount;++i) if (p->CodecSkip[i]==Id) return 1; return 0; } static bool_t UpdateOutput(player_base* p,node* From,const datadef* FromDef,int* FoundFirst) { packetformat Format; bool_t EqMissing; node* Null = NULL; node* To; node* Old = NULL; int Force = 0; int Skip = 0; pin Pin; bool_t RdOnly = (FromDef->Flags & DF_RDONLY)!=0; int Failed = 0; if (FoundFirst) *FoundFirst = 0; switch (FromDef->Type) { case TYPE_NODE: if (!RdOnly) switch (FromDef->Format1) { case IDCT_CLASS: if (VOutIDCT(p->VOutputId)) { int IDCT = VOUT_IDCT_CLASS(p->VOutputId); if (p->VideoAccel && p->Timing && p->SkipAccelFrom != From->Class) Force = IDCT; else Skip = IDCT; } break; case VOUT_CLASS: Force = p->VOutputId; break; case AOUT_CLASS: Force = p->AOutputId; break; } // already connected pointer? if (From->Get(From,FromDef->No,&Old,sizeof(Old))==ERR_NONE && Old) { if (FoundFirst) *FoundFirst = 1; if ((!Force || Old->Class == Force) && (!Skip || Old->Class != Skip)) { if (UpdateNode(p,Old)) return 1; Failed = Old->Class; } else ReleaseNode(p,Old); } if (RdOnly) return 0; if (Force) { To = GetNode(p,Force); if (To && From->Set(From,FromDef->No,&To,sizeof(To))==ERR_NONE) { ReleaseRef(p,Old); Old = To; if (FoundFirst) *FoundFirst = 1; if (UpdateNode(p,To)) return 1; } else ReleaseRef(p,To); if (FromDef->Format1 == IDCT_CLASS) p->SkipAccelFrom = From->Class; if (Old && !Failed && UpdateNode(p,Old)) return 1; } if (FromDef->Format1 != VOUT_CLASS && FromDef->Format1 != AOUT_CLASS) { int* i; array List; NodeEnumClass(&List,FromDef->Format1); for (i=ARRAYBEGIN(List,int);i!=ARRAYEND(List,int);++i) if (Failed != *i && (To = GetNode(p,*i))!=NULL) { if (From->Set(From,FromDef->No,&To,sizeof(To))==ERR_NONE) { ReleaseRef(p,Old); Old = To; if (FoundFirst) *FoundFirst = 1; if (UpdateNode(p,To)) { ArrayClear(&List); return 1; } } else ReleaseRef(p,To); } ArrayClear(&List); } From->Set(From,FromDef->No,&Null,sizeof(Null)); ReleaseRef(p,Old); break; case TYPE_COMMENT: if (!RdOnly) TryComment(p,From,FromDef); return 1; case TYPE_PACKET: EqMissing = p->Eq && !Used(p,p->Equalizer); // already connected pin? if (From->Get(From,FromDef->No,&Pin,sizeof(Pin))==ERR_NONE && Pin.Node) { if (!IsCodecSkip(p,Pin.Node->Class) && (!NodeIsClass(Pin.Node->Class,OUT_CLASS) || Pin.Node->Class == p->VOutputId || (Pin.Node->Class == p->AOutputId && !EqMissing))) { if (FoundFirst) *FoundFirst = 1; if (UpdateNode(p,Pin.Node)) return 1; Failed = Pin.Node->Class; // don't try with class again } else ReleaseNode(p,Pin.Node); if (!RdOnly) { Disconnect(From,FromDef->No,Pin.Node,Pin.No); ReleaseRef(p,Pin.Node); } } if (RdOnly) return 0; if (From->Get(From,FromDef->No|PIN_FORMAT,&Format,sizeof(Format))==ERR_NONE) { array List; const int *i; if (Format.Type == PACKET_VIDEO) { if (!p->VOutputId) { if (FoundFirst) *FoundFirst = 1; return 0; } if (TryPin(p,From,FromDef->No,p->VOutputId,FoundFirst,Failed)) return 1; } if (Format.Type == PACKET_AUDIO && !EqMissing) { if (!p->AOutputId) { if (FoundFirst) *FoundFirst = 1; return 0; } if (TryPin(p,From,FromDef->No,p->AOutputId,FoundFirst,Failed)) return 1; } PacketFormatEnumClass(&List,&Format); for (i=ARRAYBEGIN(List,int);i!=ARRAYEND(List,int);++i) if (!IsCodecSkip(p,*i) && TryPin(p,From,FromDef->No,*i,FoundFirst,Failed)) { ArrayClear(&List); return 1; } if (Format.Type == PACKET_AUDIO && EqMissing) { if (!p->AOutputId) { if (FoundFirst) *FoundFirst = 1; return 0; } if (TryPin(p,From,FromDef->No,p->AOutputId,FoundFirst,Failed)) return 1; } ArrayClear(&List); } break; } return 0; } static node* AddRef(player_base* p,node* Node) { if (Node) { bool_t Found; ref Ref; int No; Ref.RefCount = 1; if (Node->Get(Node,NODE_PARTOF,&Ref.Node,sizeof(node*)) != ERR_NONE) Ref.Node = Node; No = ArrayFind(&p->Ref,ARRAYCOUNT(p->Ref,ref),sizeof(ref),&Ref,(arraycmp)CmpRef,&Found); if (!Found) ArrayAdd(&p->Ref,ARRAYCOUNT(p->Ref,ref),sizeof(ref),&Ref,(arraycmp)CmpRef,64); else ARRAYBEGIN(p->Ref,ref)[No].RefCount++; } return Node; } static void ReleaseRef(player_base* p,node* Node) { if (Node) { int No; ref Ref; bool_t Found; if (Node->Get(Node,NODE_PARTOF,&Ref.Node,sizeof(node*)) != ERR_NONE) Ref.Node = Node; No = ArrayFind(&p->Ref,ARRAYCOUNT(p->Ref,ref),sizeof(ref),&Ref,(arraycmp)CmpRef,&Found); if (Found && --(ARRAYBEGIN(p->Ref,ref)[No].RefCount)==0) { ArrayRemove(&p->Ref,ARRAYCOUNT(p->Ref,ref),sizeof(ref),&Ref,(arraycmp)CmpRef); NodeDelete(Ref.Node); } } } static bool_t UpdateNode(player_base* p,node* Node) { bool_t Buffered; assert(NodeIsClass(Node->Class,FLOW_CLASS)); if (!NodeIsClass(Node->Class,OUT_CLASS)) { // update output pins datadef DataDef; int No; for (No=0;NodeEnum(Node,No,&DataDef)==ERR_NONE;++No) if (DataDef.Flags & DF_OUTPUT) if (!UpdateOutput(p,Node,&DataDef,NULL)) { ReleaseNode(p,Node); return 0; } } if (Node->Class == p->VOutputId) { p->VOutput = Node; Node->Set(Node,VOUT_PLAY,&p->TimerPlay,sizeof(bool_t)); Node->Get(Node,VOUT_IDCT,&p->VIDCT,sizeof(p->VIDCT)); if (!p->VIDCT) p->VideoAccel = 0; if (p->FullScreenAfterSync && !p->FullScreen) Notify(p,PLAYER_FULLSCREEN,1); } if (Node->Class == p->AOutputId) { node* Timer; p->AOutput = Node; if (Node->Get(Node,AOUT_TIMER,&Timer,sizeof(Timer))==ERR_NONE) SetTimerNode(p,Timer,1); } if (Node->Get(Node,FLOW_BUFFERED,&Buffered,sizeof(Buffered))==ERR_NONE && Buffered) p->FlowBuffer = Node; return 1; } static int ReleaseStreamsNotify(player_base* p,int Param,int Param2) { datadef DataDef; int No; for (No=0;NodeEnum((node*)p->Format,No,&DataDef)==ERR_NONE;++No) { if (DataDef.No >= FORMAT_COMMENT && DataDef.No < FORMAT_COMMENT+MAXSTREAM) NullPin((node*)p->Format,DataDef.No); if (DataDef.No >= FORMAT_STREAM && DataDef.No < FORMAT_STREAM+MAXSTREAM) ReleaseOutput(p,(node*)p->Format,&DataDef); } return ERR_NONE; } static int UpdateStreamsMask(player_base* p) { int Result = UpdateStreams(p,1,0); if (Result == ERR_NONE && p->Format) { tick_t Time; p->Timer->Get(p->Timer,TIMER_TIME,&Time,sizeof(tick_t)); p->Format->Set(p->Format,FORMAT_UPDATEMASK,&Time,sizeof(tick_t)); } return Result; } static int UpdateStreamsNotify(player_base* p,int Param,int Param2) { // called by process thread // we are inside the LockProcess so Lock/Unlock not needed // inputprocess doesn't bother us here UpdateStreams(p,-1,0); return ERR_NONE; } static void ReleaseFormat(player_base* p) { NullPin((node*)p->Format,FORMAT_GLOBAL_COMMENT); p->Format->Set(p->Format,FORMAT_INPUT,NULL,0); NodeDelete((node*)p->Format); p->Format = NULL; } static void ErrorCodec(packetformat* Format) { tchar_t Name[64]; int Id = 0; switch (Format->Type) { case PACKET_VIDEO: Id = PLAYER_VIDEO_NOT_FOUND; break; case PACKET_AUDIO: #ifdef CONFIG_GPL if (Format->Format.Audio.Format == AUDIOFMT_AAC) Id = PLAYER_AUDIO_NOT_FOUND2; else #endif Id = PLAYER_AUDIO_NOT_FOUND; break; } if (Id && PacketFormatName(Format,Name,TSIZEOF(Name))) ShowError(PLAYER_ID,PLAYER_ID,Id,Name); } static int UpdateStreams(player_base* p,int ForceRefresh,bool_t CodecSkip) { packetformat Format; datadef DataDef; int FoundCodec; int No; p->UpdateStreamsNeeded = 0; if (!p->Format) return ERR_INVALID_PARAM; // first release unused streams for (No=0;NodeEnum((node*)p->Format,No,&DataDef)==ERR_NONE;++No) if (DataDef.No >= FORMAT_STREAM && DataDef.No < FORMAT_STREAM+MAXSTREAM) { p->StreamNo = DataDef.No - FORMAT_STREAM; if (!p->StreamSkip[p->StreamNo] && p->Format->Get(p->Format,DataDef.No|PIN_FORMAT,&Format,sizeof(Format))==ERR_NONE && Format.Type != PACKET_NONE && p->Selected[Format.Type]>=0 && p->Selected[Format.Type] != p->StreamNo) ReleaseOutput(p,(node*)p->Format,&DataDef); } for (No=0;NodeEnum((node*)p->Format,No,&DataDef)==ERR_NONE;++No) { if (DataDef.No >= FORMAT_COMMENT && DataDef.No < FORMAT_COMMENT+MAXSTREAM) { p->StreamNo = DataDef.No - FORMAT_COMMENT; TryComment(p,(node*)p->Format,&DataDef); } if (DataDef.No >= FORMAT_STREAM && DataDef.No < FORMAT_STREAM+MAXSTREAM) { p->StreamNo = DataDef.No - FORMAT_STREAM; if (!p->StreamSkip[p->StreamNo] && p->Format->Get(p->Format,DataDef.No|PIN_FORMAT,&Format,sizeof(Format))==ERR_NONE && Format.Type != PACKET_NONE) { if (p->Selected[Format.Type]<0) p->Selected[Format.Type] = p->StreamNo; if (p->Selected[Format.Type] == p->StreamNo && !UpdateOutput(p,(node*)p->Format,&DataDef,&FoundCodec) && FoundCodec!=1) { if (!FoundCodec && !CodecSkip) ErrorCodec(&Format); p->Selected[Format.Type] = -1; p->StreamSkip[p->StreamNo] = 1; } } } } UpdateVideo(p,ForceRefresh); UpdateTimeouts(p); return ERR_NONE; } static int UpdateFormat(player_base* p,bool_t BufferChanged) { if (BufferChanged) p->BufferWarning = 0; p->CurrBufferSize2 = p->MicroDrive ? p->MDBufferSize2 : p->BufferSize2; p->BufferMax = p->CurrBufferSize2; if (p->Format) p->Format->Set(p->Format,FORMAT_BUFFERSIZE,&p->CurrBufferSize2,sizeof(int)); return ERR_NONE; } static void RemoveFilter(player_base* p,node* Filter,int In,int Out) { pin InPin; pin OutPin; if (Filter->Get(Filter,In,&InPin,sizeof(InPin))==ERR_NONE && InPin.Node && Filter->Get(Filter,Out,&OutPin,sizeof(OutPin))==ERR_NONE && OutPin.Node) { if (ConnectionUpdate(InPin.Node,InPin.No,OutPin.Node,OutPin.No)==ERR_NONE) { ConnectionUpdate(NULL,0,Filter,Out); ConnectionUpdate(Filter,In,NULL,0); ReleaseRef(p,Filter); } else { ConnectionUpdate(InPin.Node,InPin.No,Filter,In); ConnectionUpdate(Filter,Out,OutPin.Node,OutPin.No); } } } static void InsertFilter(player_base* p,node* Filter,int In,int Out,node* Output,int OutputIn) { pin Pin; if (Output->Get(Output,OutputIn,&Pin,sizeof(Pin))==ERR_NONE && Pin.Node) { if (ConnectionUpdate(Pin.Node,Pin.No,Filter,In)==ERR_NONE && ConnectionUpdate(Filter,Out,Output,OutputIn)==ERR_NONE) GetNode(p,Filter->Class); else { ConnectionUpdate(Pin.Node,Pin.No,Output,OutputIn); //try restoring... ConnectionUpdate(NULL,0,Filter,Out); ConnectionUpdate(Filter,In,NULL,0); } } } static int UpdateEqualizer(player_base* p) { if (p->Equalizer) p->Equalizer->Get(p->Equalizer,EQUALIZER_ENABLED,&p->Eq,sizeof(p->Eq)); if (!p->Eq && Used(p,p->Equalizer)) RemoveFilter(p,p->Equalizer,CODEC_INPUT,CODEC_OUTPUT); else if (p->Eq && !Used(p,p->Equalizer) && p->AOutput) InsertFilter(p,p->Equalizer,CODEC_INPUT,CODEC_OUTPUT,p->AOutput,OUT_INPUT); return ERR_NONE; } static void Unload(player_base* p,bool_t KeepPlay,bool_t KeepStreams,bool_t Refresh) { p->Sync = 0; p->Fill = 0; p->InSeek = 0; p->SeekAfterSync.Num = -1; if (!KeepPlay) SetPlay(p,1); if (!KeepStreams || p->Format) { int i; for (i=0;iSelected[i] = -1; } if (p->Format) ReleaseFormat(p); if (p->Input) { NodeDelete((node*)p->Input); p->Input = NULL; } if (p->InputBase) { NodeDelete((node*)p->InputBase); p->InputBase = NULL; } p->Title[0] = 0; Notify(p,PLAYER_TITLE,1); assert(ARRAYEMPTY(p->Ref)); ArrayClear(&p->Ref); ArrayClear(&p->Comment); p->Position = -1; p->NoMoreInput = 0; p->CurrentChanged = 1; p->BufferWarning = 0; p->CodecSkipCount = 0; if (!KeepPlay) UpdateTimeouts(p); UpdateRunInput(p); UpdateRunProcess(p); if (Refresh) Notify(p,PLAYER_PERCENT,1); } static void URLToTitle(tchar_t* Title, int TitleLen, const tchar_t* URL) { tchar_t Ext[MAXPATH]; tchar_t *i,*j; bool_t HasHost; i = (tchar_t*) GetMime(URL,NULL,0,&HasHost); if (i==URL || !HasHost || tcschr(i,'/') || tcschr(i,'\\')) SplitURL(URL,NULL,0,NULL,0,Title,TitleLen,Ext,TSIZEOF(Ext)); else tcscpy_s(Title,TitleLen,URL); // replace %20 and '_' with space for (j=i=Title;*i;++i) { if (*i=='_') *j++ = ' '; else if (i[0]=='%' && i[1]=='2' && i[2]=='0') { *j++ = ' '; i += 2; } else *j++ = *i; } *j=0; } static bool_t LoadPlaylist(player* Player,const array* List,stream* Input,int* Index,const tchar_t* Path,const void* Probe,int ProbeSize) { int *i; bool_t SeekHead = 0; for (i=ARRAYBEGIN(*List,int);i!=ARRAYEND(*List,int);++i) { playlist* Playlist = (playlist*)NodeCreate(*i); if (Playlist) { tchar_t Base[MAXPATH]; tchar_t Abs[MAXPATH]; tchar_t ItemPath[MAXPATH]; tchar_t ItemTitle[256]; tick_t ItemLength; int Pos = *Index; if (SeekHead) { Input->Seek(Input,ProbeSize,SEEK_SET); SeekHead = 0; } if (Probe) Playlist->Set(Playlist,PLAYLIST_DATAFEED,Probe,ProbeSize); if (Playlist->Set(Playlist,PLAYLIST_STREAM,&Input,sizeof(Input)) == ERR_NONE) { SplitURL(Path,Base,TSIZEOF(Base),Base,TSIZEOF(Base),NULL,0,NULL,0); while (Playlist->ReadList(Playlist,ItemPath,TSIZEOF(ItemPath),ItemTitle,TSIZEOF(ItemTitle),&ItemLength)==ERR_NONE) { AbsPath(Abs,TSIZEOF(Abs),ItemPath,Base); Player->Set(Player,PLAYER_LIST_URL+Pos,Abs,sizeof(Abs)); Player->Set(Player,PLAYER_LIST_TITLE+Pos,ItemTitle,sizeof(ItemTitle)); Player->Set(Player,PLAYER_LIST_LENGTH+Pos,&ItemLength,sizeof(ItemLength)); ++Pos; } SeekHead = 1; } Playlist->Set(Playlist,PLAYLIST_STREAM,NULL,0); NodeDelete((node*)Playlist); if (Pos != *Index) { *Index = Pos; return 1; } } } if (Probe) // restore file position Input->Seek(Input,ProbeSize,SEEK_SET); return 0; } static void ListSwap(player_base* p,int a,int b) { #ifdef MULTITHREAD assert(p->ProcessLocked != ThreadId() && p->InputLocked != ThreadId()); #endif LockEnter(p->Lock); if (a < p->PlayListCount && b < p->PlayListCount && a!=b) { item Tmp; Tmp = p->PlayList[a]; p->PlayList[a] = p->PlayList[b]; p->PlayList[b] = Tmp; if (p->Current == a) p->Current = b; else if (p->Current == b) p->Current = a; p->PlayList[a].Changed = 1; p->PlayList[b].Changed = 1; NotifyList(p); } LockLeave(p->Lock); } static int Load(player_base* p,bool_t Silent,bool_t OnlyLocal,bool_t Nested); static bool_t ReDirect(player_base* p,const tchar_t* New,const tchar_t* Orig,bool_t Silent,bool_t OnlyLocal,bool_t Nested,int* Result) { tchar_t Base[MAXPATH]; tchar_t Target[MAXPATH]; SplitURL(Orig,Base,TSIZEOF(Base),Base,TSIZEOF(Base),NULL,0,NULL,0); AbsPath(Target,TSIZEOF(Target),New,Base); if (tcsicmp(Target,Orig)==0) return 0; p->Player.Set(p,PLAYER_LIST_URL+p->Current,Target,sizeof(Target)); *Result = Load(p,Silent,OnlyLocal,Nested); return 1; } static int Load(player_base* p,bool_t Silent,bool_t OnlyLocal,bool_t Nested) { tchar_t ContentType[MAXPATH]; array List; block Probe; tchar_t* URL; stream* Input; int ProbeSize; int Result; int No; bool_t PosPrevKey = p->SeekAfterSyncPrevKey; fraction Pos = p->SeekAfterSync; p->SeekAfterSync.Num = -1; p->InSeek = 0; p->TemporaryHidden = Pos.Num > 0; if (p->CurrentChanged || p->PlayList[p->Current].Changed || p->IOError) { tchar_t Mime[MAXMIME]; int Length; pin Comment; bool_t Network; if (p->Format) Pos.Num = -1; // invalidate last SeekAfterSync Unload(p,1,OnlyLocal,0); // find input URL = p->PlayList[p->Current].URL; if (!URL[0]) { if (!Silent) UpdateTimeouts(p); return ERR_NONE; } GetMime(URL,Mime,TSIZEOF(Mime),&p->HasHost); Network = p->HasHost || (URL[0]=='\\' && URL[1]=='\\'); if (OnlyLocal && Network) { if (!Silent) UpdateTimeouts(p); return ERR_FILE_NOT_FOUND; } Input = GetStream(URL,Silent); if (!Input) { if (!Silent) UpdateTimeouts(p); return ERR_NOT_SUPPORTED; } if (p->PlayList[p->Current].Title[0]) tcscpy_s(p->Title,TSIZEOF(p->Title),p->PlayList[p->Current].Title); else URLToTitle(p->Title,TSIZEOF(p->Title),URL); memset(p->StreamSkip,0,sizeof(p->StreamSkip)); UpdateSpeed(p); SetTimerNode(p,NodeEnumObject(NULL,SYSTIMER_ID),0); // restore system timer p->Input = Input; Comment.No = PLAYER_COMMENT; Comment.Node = (node*)p; p->Input->Set(p->Input,STREAM_COMMENT,&Comment,sizeof(pin)); p->Input->Set(p->Input,STREAM_SILENT,&Silent,sizeof(Silent)); Result = p->Input->Set(p->Input,STREAM_URL,URL,TSIZEOF(p->PlayList[p->Current].URL)); if (Result != ERR_NONE) { Unload(p,Silent,0,1); return Result; } newstream: if (p->Input->Get(p->Input,STREAM_LENGTH,&Length,sizeof(int))!=ERR_NONE) Length = -1; // find format if (!AllocBlock(PROBE_SIZE,&Probe,0,HEAP_ANY)) { Unload(p,Silent,0,1); return ERR_OUT_OF_MEMORY; } ProbeSize = p->Input->ReadBlock(p->Input,&Probe,0,PROBE_SIZE); // important: get content type after probe was read if (p->Input->Get(p->Input,STREAM_CONTENTTYPE,ContentType,sizeof(ContentType))!=ERR_NONE) ContentType[0] = 0; if (ProbeSize > 0) { int *i; if (!p->InputBase) { node* Process = NULL; NodeEnumClassEx(&List,STREAMPROCESS_CLASS,ContentType,URL,Probe.Ptr,ProbeSize); for (i=ARRAYBEGIN(List,int);i!=ARRAYEND(List,int);++i) { Process = NodeCreate(*i); if (Process) { Process->Set(Process,STREAM_COMMENT,&Comment,sizeof(pin)); Process->Set(Process,STREAM_SILENT,&Silent,sizeof(Silent)); if (Process->Set(Process,STREAMPROCESS_INPUT,&p->Input,sizeof(node*)) == ERR_NONE) break; NodeDelete(Process); } } ArrayClear(&List); if (Process) { p->InputBase = p->Input; p->Input = (stream*)Process; FreeBlock(&Probe); goto newstream; } } NodeEnumClassEx(&List,FORMAT_CLASS,ContentType,URL,Probe.Ptr,ProbeSize); for (i=ARRAYBEGIN(List,int);i!=ARRAYEND(List,int);++i) { p->Format = (format*)NodeCreate(*i); if (p->Format) { notify Notify; int Align = p->HasHost?1:ALIGN_SIZE; bool_t FindSubtitles = !p->HasHost; UpdateFormat(p,0); // set buffersize as soon as possible p->Format->Set(p->Format,FORMAT_AUTO_READSIZE,&Network,sizeof(bool_t)); p->Format->Set(p->Format,FORMAT_FILEALIGN,&Align,sizeof(Align)); p->Format->Set(p->Format,FORMAT_DATAFEED,Probe.Ptr,ProbeSize); p->Format->Set(p->Format,FORMAT_FIND_SUBTITLES,&FindSubtitles,sizeof(FindSubtitles)); p->Format->Set(p->Format,FORMAT_GLOBAL_COMMENT,&Comment,sizeof(pin)); Notify.This = p; Notify.Func = (notifyfunc)UpdateStreamsNotify; p->Format->Set(p->Format,FORMAT_UPDATESTREAMS,&Notify,sizeof(Notify)); Notify.This = p; Notify.Func = (notifyfunc)ReleaseStreamsNotify; p->Format->Set(p->Format,FORMAT_RELEASESTREAMS,&Notify,sizeof(Notify)); if (p->Format->Set(p->Format,FORMAT_INPUT,&p->Input,sizeof(stream*)) == ERR_NONE) break; ReleaseFormat(p); if (p->Input->Seek(p->Input,0,SEEK_CUR) > PROBE_SIZE) //maybe less if file smaller as PROBE_SIZE p->Input->Seek(p->Input,PROBE_SIZE,SEEK_SET); } } ArrayClear(&List); if (!p->Format && !Nested) { NodeEnumClassEx(&List,PLAYLIST_CLASS,ContentType,URL,Probe.Ptr,ProbeSize); if (!ARRAYEMPTY(List)) { int Index = p->PlayListCount; if (LoadPlaylist(&p->Player,&List,p->Input,&Index,URL,Probe.Ptr,ProbeSize)) { FreeBlock(&Probe); // remove this from playlist for (No=p->Current+1;NoPlayListCount;++No) ListSwap(p,No-1,No); SetPlayListCount(p,p->PlayListCount-1); return Load(p,Silent,OnlyLocal,1); } } ArrayClear(&List); } } FreeBlock(&Probe); if (!p->Format) { Unload(p,0,0,1); if (ProbeSize < 0) ShowError(0,ERR_ID,ERR_FILE_NOT_FOUND,URL); else if (!CheckExts(URL,T("exe:P"))) ShowError(PLAYER_ID,PLAYER_ID,PLAYER_FORMAT_NOT_FOUND,URL); return ERR_NOT_SUPPORTED; } if (p->Player.CommentByName(p,-1,PlayerComment(COMMENT_REDIRECT),ContentType,TSIZEOF(ContentType)) && ReDirect(p,ContentType,URL,Silent,OnlyLocal,Nested,&Result)) return Result; // only after format initialized (may have reopened http connection with different pragma) p->Streaming = p->HasHost && (p->Input->Get(p->Input,STREAM_LENGTH,&Length,sizeof(int))!=ERR_NONE || Length<0); p->Timing = 1; p->Format->Get(p->Format,FORMAT_TIMING,&p->Timing,sizeof(p->Timing)); p->SkipAccelFrom = 0; p->PlayList[p->Current].Changed = 0; p->CurrentChanged = p->HasHost; // force reload when example connection stops p->NoMoreInput = 0; p->Position = 0; p->BufferMax = p->CurrBufferSize2; p->Sync = 1; // start with syncing p->PosNotify = -1; p->MinBuffer = 0; Notify(p,PLAYER_TITLE,1); Flush(p); if (Pos.Num > 0) { p->SeekAfterSync = Pos; // restore syncaftersync (now we are sure there will a synced event) p->SeekAfterSyncPrevKey = PosPrevKey; } StopLoadMode(p); UpdatePlay(p); } else { UpdateFormat(p,0); UpdateStreams(p,0,0); ClearStats(p); if (Pos.Num > 0) Result = Seek2(p,-1,&Pos,1); else Result = Seek2(p,0,NULL,0); if (Result != ERR_NONE) return Result; } UpdateRunInput(p); UpdateRunProcess(p); UpdateTimeouts(p); #ifdef MULTITHREAD EventSet(p->EventProcess); // wakeup input threads (if needed) #else p->WaitForProcess = 0; #endif return ERR_NONE; } static int InputThread(player_base* p); static int ProcessThread(player_base* p); static void Unlock(player_base* p, bool_t Input) { #ifdef MULTITHREAD //DEBUG_MSG2(DEBUG_PLAYER,T("Unlocked %d %08x"),p->LockCount,GetCurrentThreadId()); if (Input) { LockLeave(p->LockInput); --p->LockInputCount; assert(p->LockInputCount>=0); } LockLeave(p->LockProcess); --p->LockProcessCount; assert(p->LockProcessCount>=0); LockLeave(p->Lock); #endif } static void Lock(player_base* p, bool_t Input) { #ifdef MULTITHREAD //DEBUG_MSG2(DEBUG_PLAYER,T("Locked Start %d %08x"),p->LockCount,GetCurrentThreadId()); assert(p->ProcessLocked != ThreadId() && p->InputLocked != ThreadId()); LockEnter(p->Lock); p->LockProcessCount++; LockEnter(p->LockProcess); if (Input) { p->LockInputCount++; LockEnter(p->LockInput); } //DEBUG_MSG(DEBUG_PLAYER,T("Locked End")); #endif } static int UpdateWnd(player_base* p,void* Wnd) { if (Wnd != p->Wnd) { p->Wnd = Wnd; if (p->Wnd) { #ifdef MULTITHREAD p->InputThread = ThreadCreate(InputThread,p,25); p->ProcessThread = ThreadCreate(ProcessThread,p,25); p->InputPriority = 0; p->ProcessPriority = 0; #endif Lock(p,1); if (p->PlayListCount && !p->DiscardList) Load(p,1,1,0); else { SetPlayListCount(p,0); p->SeekAfterSync.Num = -1; } Unlock(p,1); } else { Lock(p,1); if (!p->KeepList) SetPlayListCount(p,0); #ifndef REGISTRY_GLOBAL NodeRegSave((node*)p); NodeRegSave(p->Color); NodeRegSave(p->Equalizer); #endif Unlock(p,1); #ifdef MULTITHREAD p->RunProcess = 0; ThreadPriority(p->ProcessThread,0); EventSet(p->EventRunProcess); ThreadTerminate(p->ProcessThread); p->ProcessThread = NULL; p->RunInput = 0; ThreadPriority(p->InputThread,0); EventSet(p->EventProcess); EventSet(p->EventRunInput); ThreadTerminate(p->InputThread); p->InputThread = NULL; #endif Unload(p,0,0,0); } } return ERR_NONE; } static int NextPrev(player_base* p,int Dir,bool_t StopIfEnd,bool_t NotTheSame) { if (p->PlayListCount > 0) { int Index = FindCurrentIndex(p); int Tries; for (Tries=NotTheSame?1:0;TriesPlayListCount;++Tries) { Index += Dir; if (Index >= p->PlayListCount || Index < 0) { if (StopIfEnd && p->ExitAtEnd) Notify(p,PLAYER_EXIT_AT_END,0); if (StopIfEnd && !p->Repeat) { SetPlay(p,1); Notify(p,PLAYER_PERCENT,0); return ERR_NONE; } RandomizePlayIndex(p,0); Index -= Dir*p->PlayListCount; } if (p->Current != p->PlayIndex[Index]) { p->Current = p->PlayIndex[Index]; p->CurrentChanged = 1; } if (Load(p,1,0,0) == ERR_NONE) return ERR_NONE; if (!Dir) break; } SetPlay(p,1); Notify(p,PLAYER_PERCENT,0); } else Unload(p,0,0,1); return ERR_NEED_MORE_DATA; } static int ProcessThread(player_base* p) { int Result = ERR_NONE; #ifdef MULTITHREAD SAFE_BEGIN while (p->Wnd) { LockEnter(p->LockProcess); #ifndef NDEBUG p->ProcessLocked = ThreadId(); #endif #endif if (p->RunProcess) { processstate State; State.Fill = p->Fill; p->Timer->Get(p->Timer,TIMER_TIME,&State.Time,sizeof(tick_t)); //DEBUG_MSG1(DEBUG_PLAYER,T("Process Time:%d"),State.Time); Result = p->Format->Process(p->Format,&State); if (Result == ERR_SYNCED) { DEBUG_MSG3(DEBUG_PLAYER,T("Synced: %d (%d,%d)"),State.Time,p->SeekAfterSync.Num,p->SeekAfterSync.Den); p->Sync = 0; p->Position = State.Time; if (State.Time < 0) State.Time = 0; p->Timer->Set(p->Timer,TIMER_TIME,&State.Time,sizeof(tick_t)); if (!p->Bench && p->SeekAfterSync.Num<0) { // enter fill mode (don't stop processing, even if playing if off) p->Fill = 1; } UpdateBuffer(p); UpdatePlay(p); UpdateRunProcess(p); UpdateTimeouts(p); // we are ready to check VOutput and update timeouts } else if (p->Fill && (Result == ERR_END_OF_FILE || Result == ERR_BUFFER_FULL || (Result == ERR_NEED_MORE_DATA && (p->NoMoreInput || State.BufferUsedAfter >= p->CurrBufferSize2-2)))) { DEBUG_MSG1(DEBUG_PLAYER,T("Filled: %d"),State.Time); if (Result == ERR_END_OF_FILE || Result == ERR_NEED_MORE_DATA) Result = ERR_BUFFER_FULL; p->Fill = 0; UpdatePlay(p); UpdateRunProcess(p); } if (Result == ERR_NEED_MORE_DATA) { p->BufferMax = p->CurrBufferSize2; if (p->NoMoreInput) { // IOError (Format doesn't know about NoMoreInput) Result = ERR_END_OF_FILE; } #ifdef MULTITHREAD else if (!p->LoadMode && (p->Play || p->FFwd) && !p->Bench && !p->Sync && !p->Fill) { if (State.BufferUsedAfter >= (p->CurrBufferSize2>>1)) { if (!p->BufferWarning && State.BufferUsedAfter >= p->CurrBufferSize2-2) { p->BufferWarning = 1; ShowError(PLAYER_ID,PLAYER_ID,PLAYER_BUFFER_WARNING); } } else { DEBUG_MSG(DEBUG_TEST,T("LoadMode Enter")); p->LoadMode = 1; p->Fill = 1; UpdatePlay(p); // UpdatetPriority(p) called too UpdateRunProcess(p); UpdateRunInput(p); Notify(p,PLAYER_LOADMODE,p->LoadMode); } } #endif } if (!p->Sync && p->Position>=0) p->Position = State.Time; #ifdef MULTITHREAD LockLeave(p->LockProcess); #ifndef NDEBUG p->ProcessLocked = 0; #endif #endif if (p->UpdateStreamsNeeded) { Lock(p,1); UpdateStreams(p,-1,1); Seek2(p,p->Position>=0?p->Position:0,NULL,0); Unlock(p,1); Result = ERR_NONE; } switch (Result) { case ERR_SYNCED: Notify(p,PLAYER_TITLE,0); if (p->Current < p->PlayListCount && !p->PlayList[p->Current].Changed && p->PlayList[p->Current].Length<0) { tick_t Duration; Lock(p,0); if (p->Format && p->Format->Get(p->Format,FORMAT_DURATION,&Duration,sizeof(Duration))==ERR_NONE) UpdateListLength(p,Duration); Unlock(p,0); } if (p->TemporaryHidden) { Lock(p,0); p->TemporaryHidden = 0; UpdateVideo(p,p->SeekAfterSync.Num>=0?-1:0); Unlock(p,0); } if (!p->VOutput && p->FullScreen) Notify(p,PLAYER_FULLSCREEN,0); p->FullScreenAfterSync = 0; p->PosNotify = State.Time; if (p->SeekAfterSync.Num>=0) { Lock(p,1); if (p->SeekAfterSync.Num>=0) // may have changed { fraction Pos; Pos = p->SeekAfterSync; p->SeekAfterSync.Num = -1; Seek2(p,-1,&Pos,p->SeekAfterSyncPrevKey); } Unlock(p,1); } break; case ERR_END_OF_FILE: if ((p->Play || p->FFwd || p->Sync) && !p->InSeek) { Lock(p,1); if (p->Bench || p->IOError) SetPlay(p,1); // stop benchmarking and show results else { tick_t Duration; bool_t NotTheSame = p->Sync || (State.Time < TICKSPERSEC && p->Format && (p->Format->Get(p->Format,FORMAT_DURATION,&Duration,sizeof(Duration))!=ERR_NONE || Duration > TICKSPERSEC*2)); NextPrev(p,1,1,NotTheSame); } Unlock(p,1); } break; case ERR_NEED_MORE_DATA: //DEBUG_MSG(DEBUG_PLAYER,T("Process Needs More Data Sleep")); #ifdef MULTITHREAD if (p->InputPriority>0) { p->InputPriority = 0; ThreadPriority(p->InputThread,p->InputPriority); } p->WaitForProcess = 0; EventSet(p->EventProcess); // signal InputThread to coninue ThreadSleep(p->Sync?4:25); #else p->WaitForProcess = 0; InputThread(p); Result = ERR_NONE; // should not return ERR_BUFFER_FULL #endif break; case ERR_BUFFER_FULL: // no packets processed, we should wait //DEBUG_MSG(DEBUG_PLAYER,T("Process Buffer Full Sleep")); #ifdef MULTITHREAD ThreadSleep(4); // this should be more intelligent... #else if (!p->WaitForProcess) Result = InputThread(p); #endif break; default: #ifdef MULTITHREAD if (p->WaitForProcess && State.BufferUsedAfterEventProcess); // signal InputThread to coninue if (p->Sync) ThreadSleep(5); // invalid media could block everything (going through the file) } if (p->LockProcessCount > 0 || (!p->Sync && (State.BufferUsedAfterUsedEnough2 || p->Clipping || !p->TimerPlay))) { DEBUG_MSG(DEBUG_PLAYER,T("Process Sleep")); ThreadSleep(0); } #else if (p->WaitForProcess && State.BufferUsedAfterWaitForProcess = 0; if (!p->WaitForProcess && Result != ERR_DROPPING && !p->Sync && !p->Fill) InputThread(p); #endif break; } if (State.Time >= 0 && State.Time >= p->PosNotify && !p->Sync && !p->Bench && p->SeekAfterSync.Num<0) { // should be last, because notify handler could call any player function (load,unload,etc...) p->PosNotify = State.Time + p->PosNotifyStep; Notify(p,PLAYER_PERCENT,1); } } #ifdef MULTITHREAD else { LockLeave(p->LockProcess); #ifndef NDEBUG p->ProcessLocked = 0; #endif EventWait(p->EventRunProcess,-1); } } SAFE_END return 0; } #else else { if (p->RunInput && !p->WaitForProcess) Result = InputThread(p); // no decoding process -> still read input in background else Result = ERR_STOPPED; } return Result; } #endif static int InputThread(player_base* p) { int Result = ERR_NONE; #ifdef MULTITHREAD SAFE_BEGIN p->IOError = 0; while (p->Wnd) { LockEnter(p->LockInput); #ifndef NDEBUG p->InputLocked = ThreadId(); #endif #else if (!p->WaitForProcess) #endif if (p->RunInput) { int Used; Result = p->Format->Read(p->Format,p->BufferMax,&Used); if (Result == ERR_DEVICE_ERROR) { if (++p->IOError >= 20) Result = ERR_END_OF_FILE; } else p->IOError = 0; if (Result == ERR_END_OF_FILE) { p->NoMoreInput = 1; UpdateRunInput(p); } //DEBUG_MSG3(-1,T("Input read %d (%d) max:%d"),Used,Result,p->BufferMax); #ifdef MULTITHREAD LockLeave(p->LockInput); #ifndef NDEBUG p->InputLocked = 0; #endif #endif switch (Result) { #ifdef MULTITHREAD case ERR_DEVICE_ERROR: ThreadSleep(100); break; #endif case ERR_NEED_MORE_DATA: ThreadSleep(2); break; case ERR_BUFFER_FULL: if (p->LoadMode) { Lock(p,0); StopLoadMode(p); Unlock(p,0); } if (p->MicroDrive && p->BufferMax==p->CurrBufferSize2) { // buffer fully loaded, wait until it's almost empty p->BufferMax = p->BurstStart; //DEBUG_MSG1(-1,T("MicroDrive Full: new max:%d"),p->BufferMax); } DEBUG_MSG(DEBUG_PLAYER,T("Input waiting for process")); p->WaitForProcess = 1; #ifdef MULTITHREAD EventWait(p->EventProcess,p->RunProcess?2000:-1); p->WaitForProcess = 0; DEBUG_MSG(DEBUG_PLAYER,T("Input waiting over")); #endif break; case ERR_END_OF_FILE: if (p->LoadMode) { Lock(p,0); StopLoadMode(p); Unlock(p,0); } Result = ERR_NONE; //no break; default: if (p->BufferMax != p->CurrBufferSize2) { // microdrive started loading, change back to full buffer //DEBUG_MSG(-1,T("MicroDrive needs data")); p->BufferMax = p->CurrBufferSize2; } if (p->LoadMode && !p->Fill && Used >= (p->VOutput ? (p->Streaming ? VIDEO_STREAMING_UNDERRUN:((p->CurrBufferSize2 * p->UnderRun)/PERCENT_ONE)) : ((p->AudioUnderRun*1024)/BLOCKSIZE))) { Lock(p,0); StopLoadMode(p); Unlock(p,0); } #ifdef MULTITHREAD if (p->MicroDrive && Used >= (MINBUFFER/BLOCKSIZE)) ThreadSleep(2); // try to give some time to ProcessThread during read bursts else if (p->LockInputCount > 0 || (Used>=p->UsedEnough2+p->MinBuffer*2)) ThreadSleep(0); #endif break; } } #ifdef MULTITHREAD else { LockLeave(p->LockInput); #ifndef NDEBUG p->InputLocked = 0; #endif EventWait(p->EventRunInput,-1); } } SAFE_END return 0; } #else return Result; } #endif static void AddComment(player_base* p,int Stream,const tchar_t* Comment) { const tchar_t* s = tcschr(Comment,'='); if (s) { uint8_t *i; tchar_t Null = 0; size_t Name = s-Comment; ++s; LockEnter(p->LockComment); for (i=ARRAYBEGIN(p->Comment,uint8_t);i!=ARRAYEND(p->Comment,uint8_t);i=(uint8_t*)CommentNext(i)) if (CommentStream(i) == Stream && Name==(int)tcslen(CommentName(i)) && tcsnicmp(CommentName(i),Comment,Name)==0) { const uint8_t* Next = CommentNext(i); if (Next == ARRAYEND(p->Comment,uint8_t)) { ArrayAppend(&p->Comment,NULL,i-Next,512); //shrink break; } *(tchar_t*)i = (tchar_t)INVALID_COMMENT; //invalidate old comment } ArrayAppend(&p->Comment,&Stream,sizeof(tchar_t),512); ArrayAppend(&p->Comment,Comment,sizeof(tchar_t)*Name,512); ArrayAppend(&p->Comment,&Null,sizeof(tchar_t),512); ArrayAppend(&p->Comment,s,sizeof(tchar_t)*(tcslen(s)+1),512); LockLeave(p->LockComment); if (tcsnicmp(Comment,PlayerComment(COMMENT_TITLE),Name)==0 || tcsnicmp(Comment,PlayerComment(COMMENT_ARTIST),Name)==0 || tcsnicmp(Comment,PlayerComment(COMMENT_AUTHOR),Name)==0) UpdateListTitle(p); } } static tick_t GetPosition(player_base* p,tick_t* OutDuration) { tick_t Duration = 1; tick_t Time = p->Timing?p->Position:-1; if (p->Format && p->Format->Get(p->Format,FORMAT_DURATION,&Duration,sizeof(Duration))==ERR_NONE && Duration>=0 && Time>=0) { if (p->InSeek && p->InSeekPos.Num>=0) Time = Scale(Duration,p->InSeekPos.Num,p->InSeekPos.Den); else if (p->SeekAfterSync.Num>=0) Time = Scale(Duration,p->SeekAfterSync.Num,p->SeekAfterSync.Den); } if (OutDuration) *OutDuration = Duration; return Time; } static int GetTimerString(player_base* p,tchar_t* Out,int OutLen) { tick_t Duration; tick_t Time = GetPosition(p,&Duration); if (Time<0) Out[0] = 0; else { if (p->TimerLeft) { Time = Time - Duration; if (Time >= 0) Time = -1; } TickToString(Out,OutLen,Time,0,0,0); } return ERR_NONE; } static int UpdateAllVideoQuality(player_base* p) { const ref *i; for (i=ARRAYBEGIN(p->Ref,ref);i!=ARRAYEND(p->Ref,ref);++i) UpdateVideoQuality(p,i->Node); return ERR_NONE; } static int Get(player_base* p, int No, void* Data, int Size) { int Result = ERR_INVALID_PARAM; #ifdef MULTITHREAD assert(p->ProcessLocked != ThreadId() && p->InputLocked != ThreadId()); #endif LockEnter(p->Lock); if (No >= PLAYER_ARRAY) { if (No >= PLAYER_LIST_URL && No < PLAYER_LIST_URL+p->PlayListCount) { No -= PLAYER_LIST_URL; tcscpy_s((tchar_t*)Data,Size/sizeof(tchar_t),p->PlayList[No].URL); Result = ERR_NONE; } else if (No >= PLAYER_LIST_TITLE && No < PLAYER_LIST_TITLE+p->PlayListCount) { No -= PLAYER_LIST_TITLE; tcscpy_s((tchar_t*)Data,Size/sizeof(tchar_t),p->PlayList[No].Title); Result = ERR_NONE; } else if (No >= PLAYER_LIST_AUTOTITLE && No < PLAYER_LIST_AUTOTITLE+p->PlayListCount) { No -= PLAYER_LIST_AUTOTITLE; if (p->PlayList[No].Title[0]) tcscpy_s((tchar_t*)Data,Size/sizeof(tchar_t),p->PlayList[No].Title); else URLToTitle((tchar_t*)Data,Size/sizeof(tchar_t),p->PlayList[No].URL); Result = ERR_NONE; } else if (No >= PLAYER_LIST_LENGTH && No < PLAYER_LIST_LENGTH+p->PlayListCount) { No -= PLAYER_LIST_LENGTH; *(tick_t*)Data = p->PlayList[No].Length; Result = ERR_NONE; } } else switch (No) { case PLAYER_VIDEO_OVERLAY: GETVALUE(p->Overlay,bool_t); break; case PLAYER_BUFFER_SIZE: GETVALUE((p->BufferSize2*BLOCKSIZE)/1024,int); break; case PLAYER_MD_BUFFER_SIZE: GETVALUE((p->MDBufferSize2*BLOCKSIZE)/1024,int); break; case PLAYER_BURSTSTART: GETVALUE((p->BurstStart*BLOCKSIZE)/1024,int); break; case PLAYER_MICRODRIVE: GETVALUE(p->MicroDrive,bool_t); break; case PLAYER_UNDERRUN: GETVALUE(p->UnderRun,int); break; case PLAYER_AUDIO_UNDERRUN: GETVALUE(p->AudioUnderRun,int); break; case PLAYER_REPEAT: GETVALUE(p->Repeat,bool_t); break; case PLAYER_SHUFFLE: GETVALUE(p->Shuffle,bool_t); break; case PLAYER_PLAYATOPEN: GETVALUE(p->PlayAtOpen,bool_t); break; case PLAYER_PLAYATOPEN_FULL: GETVALUE(p->PlayAtOpenFull,bool_t); break; case PLAYER_EXIT_AT_END: GETVALUE(p->ExitAtEnd,bool_t); break; case PLAYER_KEEPPLAY_VIDEO: GETVALUE(p->KeepPlayVideo,bool_t); break; case PLAYER_SHOWINBACKGROUND: GETVALUE(p->ShowInBackground,bool_t); break; case PLAYER_SINGLECLICKFULLSCREEN: GETVALUE(p->SingleClickFullScreen,bool_t); break; case PLAYER_KEEPPLAY_AUDIO: GETVALUE(p->KeepPlayAudio,bool_t); break; case PLAYER_KEEPLIST: GETVALUE(p->KeepList,bool_t); break; case PLAYER_MOVEBACK_STEP: GETVALUE(p->MoveBack,tick_t); break; case PLAYER_MOVEFFWD_STEP: GETVALUE(p->MoveFFwd,tick_t); break; case PLAYER_PLAY: GETVALUE(p->Play,bool_t); break; case PLAYER_FFWD: GETVALUE(p->FFwd,bool_t); break; case PLAYER_PLAY_SPEED: GETVALUE(p->PlaySpeed,fraction); break; case PLAYER_FFWD_SPEED: GETVALUE(p->FFwdSpeed,fraction); break; case PLAYER_TITLE: GETSTRING(p->Title); break; case PLAYER_INPUT: GETVALUE(p->Input,stream*); break; case PLAYER_FORMAT: GETVALUE(p->Format,format*); break; case PLAYER_AOUTPUTID: GETVALUE(p->AOutputId,int); break; case PLAYER_VOUTPUTID: GETVALUE(p->VOutputId,int); break; case PLAYER_AOUTPUTID_MAX: GETVALUE(p->AOutputIdMax,int); break; case PLAYER_VOUTPUTID_MAX: GETVALUE(p->VOutputIdMax,int); break; case PLAYER_AOUTPUT: GETVALUE(p->AOutput,node*); break; case PLAYER_VOUTPUT: GETVALUE(p->VOutput,node*); break; case PLAYER_LIST_COUNT: GETVALUE(p->PlayListCount,int); break; case PLAYER_LIST_CURRENT: GETVALUE(p->Current,int); break; case PLAYER_LIST_CURRIDX: GETVALUE(FindCurrentIndex(p),int); break; case PLAYER_VIDEO_ACCEL: GETVALUE(p->VideoAccel,bool_t); break; case PLAYER_AUDIO_QUALITY: GETVALUE(p->AudioQuality,int); break; case PLAYER_FULLSCREEN: GETVALUE(p->FullScreen,bool_t); break; case PLAYER_AUTOPREROTATE: GETVALUE(p->AutoPreRotate,bool_t); break; case PLAYER_SKIN_VIEWPORT: GETVALUE(p->SkinViewport,rect); break; case PLAYER_CLIPPING: GETVALUE(p->Clipping,bool_t); break; case PLAYER_ASPECT: GETVALUE(p->Aspect,fraction); break; case PLAYER_STEREO: GETVALUE(p->Stereo,int); break; case PLAYER_FULL_ZOOM: GETVALUE(p->FullZoom,fraction); break; case PLAYER_SKIN_ZOOM: GETVALUE(p->SkinZoom,fraction); break; case PLAYER_SMOOTH50: GETVALUE(p->SmoothZoom50,bool_t); break; case PLAYER_SMOOTHALWAYS: GETVALUE(p->SmoothZoomAlways,bool_t); break; case PLAYER_FULL_DIR: GETVALUE(p->FullDir,int); break; case PLAYER_SKIN_DIR: GETVALUE(p->SkinDir,int); break; case PLAYER_REL_DIR: GETVALUE(p->RelDir,int); break; case PLAYER_BENCHMARK_SRC: GETVALUE(p->BenchSrc,point); break; case PLAYER_BENCHMARK_DST: GETVALUE(p->BenchDst,point); break; case PLAYER_BENCHMARK: GETVALUECOND(p->BenchTime,tick_t,!p->Bench); break; case PLAYER_POSITION: GETVALUECOND(GetPosition(p,NULL),tick_t,p->Position>=0); break; case PLAYER_LOADMODE: GETVALUE(p->LoadMode,bool_t); break; case PLAYER_NOTIFY: GETVALUE(p->Notify,notify); break; case PLAYER_LIST_NOTIFY: GETVALUE(p->ListNotify,notify); break; case PLAYER_VSTREAM: GETVALUE(p->Selected[PACKET_VIDEO],int); break; case PLAYER_ASTREAM: GETVALUE(p->Selected[PACKET_AUDIO],int); break; case PLAYER_SUBSTREAM: GETVALUE(p->Selected[PACKET_SUBTITLE],int); break; case PLAYER_SYNCING: GETVALUE(p->Sync || !p->Format,bool_t); break; case PLAYER_BACKGROUND: GETVALUE(p->Background,bool_t); break; case PLAYER_FOREGROUND: GETVALUE(p->Foreground,bool_t); break; case PLAYER_STREAMING: GETVALUE(p->Streaming,bool_t) break; case PLAYER_INSEEK: GETVALUE(p->InSeek,bool_t); break; case PLAYER_DISCARDLIST: GETVALUE(p->DiscardList,bool_t); break; case PLAYER_VIDEO_QUALITY: GETVALUE(p->VideoQuality,int); if (!SupportVQ(p)) Result = ERR_NOT_SUPPORTED; break; case PLAYER_PERCENT: if (p->Format && p->Input) { fraction* Percent = (fraction*)Data; tick_t Duration; int FilePos; int FileSize; tick_t Position = p->Position; if (p->InSeek && p->InSeekPos.Num>=0) { Percent->Num = p->InSeekPos.Num; Percent->Den = p->InSeekPos.Den; Result = ERR_NONE; } else if (p->SeekAfterSync.Num>=0) { Percent->Num = p->SeekAfterSync.Num; Percent->Den = p->SeekAfterSync.Den; Result = ERR_NONE; } else if (Position >= 0 && p->Format->Get(p->Format,FORMAT_DURATION,&Duration,sizeof(Duration))==ERR_NONE) { UpdateListLength(p,Duration); Percent->Num = Position; Percent->Den = Duration; Result = ERR_NONE; } else if (Position==0 && !p->Streaming && p->Timing) { Percent->Num = 0; Percent->Den = 0; Result = ERR_NONE; } else if (p->Input->Get(p->Input,STREAM_LENGTH,&FileSize,sizeof(FileSize))==ERR_NONE && p->Format->Get(p->Format,FORMAT_FILEPOS,&FilePos,sizeof(FilePos))==ERR_NONE) { Percent->Num = FilePos; Percent->Den = FileSize; Result = ERR_NONE; } } break; case PLAYER_CURRENTDIR: GETSTRING(p->CurrentDir); break; case PLAYER_MUTE: GETVALUE(RefreshAudio(p)->Mute,bool_t); break; case PLAYER_VOLUME: GETVALUE(RefreshAudio(p)->Volume,int); break; case PLAYER_PAN: GETVALUE(RefreshAudio(p)->Pan,int); break; case PLAYER_PREAMP: GETVALUE(p->PreAmp,int); break; case PLAYER_TIMER_LEFT: GETVALUE(p->TimerLeft,bool_t); break; case PLAYER_TIMER: Result = GetTimerString(p,Data,Size/sizeof(tchar_t)); break; case PLAYER_DURATION: if (p->Format) { Result = p->Format->Get(p->Format,FORMAT_DURATION,Data,Size); if (Result == ERR_NONE) UpdateListLength(p,*(tick_t*)Data); } break; } LockLeave(p->Lock); return Result; } static int UpdateInSeek(player_base* p) { if (p->Format && p->Input && p->Timer) { fraction Pos = p->SeekAfterSync; p->InSeekPos.Num = -1; p->SeekAfterSync.Num = -1; if (!p->InSeek) { if (Pos.Num>=0) { Lock(p,1); Seek2(p,-1,&Pos,p->SeekAfterSyncPrevKey); Unlock(p,1); } else Notify(p,PLAYER_PERCENT,0); } UpdatePlay(p); } return ERR_NONE; } static int AddCodecSkip(player_base* p,const pin* Pin) { packetformat Format; if (Pin->Node) { if (p->CodecSkipCount < MAXSKIP && Pin->Node->Get(Pin->Node,Pin->No|PIN_FORMAT,&Format,sizeof(Format))==ERR_NONE) { array List; const int *i; PacketFormatEnumClass(&List,&Format); for (i=ARRAYBEGIN(List,int);i!=ARRAYEND(List,int);++i) if (!IsCodecSkip(p,*i) && *i != Pin->Node->Class) { // found an alternative ArrayClear(&List); p->CodecSkip[p->CodecSkipCount++] = Pin->Node->Class; p->UpdateStreamsNeeded = 1; return ERR_NONE; } ArrayClear(&List); } else { datadef DataDef; if (NodeDataDef(Pin->Node,Pin->No,&DataDef) && DataDef.Type==TYPE_NODE && DataDef.Format1==IDCT_CLASS && p->VideoAccel && p->SkipAccelFrom != Pin->Node->Class) { p->SkipAccelFrom = Pin->Node->Class; p->UpdateStreamsNeeded = 1; return ERR_NONE; } } } return ERR_NOT_SUPPORTED; } static int SetCurrent(player_base* p,int i,bool_t Index) { if (i>=p->PlayListCount) return ERR_INVALID_PARAM; if (i<0) Unload(p,0,0,1); else { if (p->Current != i) { p->Current = i; p->CurrentChanged = 1; if (Index) RandomizePlayIndex(p,1); } if (p->Wnd) { int Result = Load(p,0,0,0); if (Result == ERR_FILE_NOT_FOUND) Result = NextPrev(p,1,1,1); if (Result==ERR_NONE && p->Format && (p->Streaming || p->PlayAtOpen || p->PlayAtOpenFull)) { p->FullScreenAfterSync = p->PlayAtOpenFull; if (p->FullScreenAfterSync && IsVideo(p) && !p->FullScreen) Notify(p,PLAYER_FULLSCREEN,1); p->Play = 1; p->FFwd = 0; SetPlay(p,0); } } } return ERR_NONE; } static NOINLINE bool_t CloseTo(tick_t Prev,tick_t Chapter,tick_t Pos) { tick_t Limit; if (Prev<0) return 0; Limit = abs(Chapter-Prev)/32; if (Limit>10*TICKSPERSEC) Limit=10*TICKSPERSEC; return abs(Pos-Chapter)Position>=0) { if (Dir<0) for (No=MAXCHAPTER-1;No>=0;--No) { tick_t Prev = PlayerGetChapter(&p->Player,No-1,NULL,0); tick_t Chapter = PlayerGetChapter(&p->Player,No,NULL,0); if (Chapter >= 0 && Chapter < p->Position && !CloseTo(Prev,Chapter,p->Position)) return Chapter; } else { tick_t Prev = -1; for (No=1;NoPlayer,No,NULL,0); if (Chapter >= 0 && Chapter > p->Position && !CloseTo(Prev,Chapter,p->Position)) return Chapter; Prev = Chapter; } } } return -1; } static int UpdateShowInBackground(player_base* p) { UpdateBackground(p); return UpdateVideo(p,0); } static void UpdateSoftVolume(player_base* p) { bool_t SoftVolume = QueryAdvanced(ADVANCED_SYSTEMVOLUME); if (p->SoftVolume != SoftVolume) { p->SoftVolume = SoftVolume; Lock(p,0); UpdateAudio(p); Unlock(p,0); } } static int Set(player_base* p, int No, const void* Data, int Size) { tick_t t; int New,Old; int Result = ERR_INVALID_PARAM; if (No >= PLAYER_COMMENT && No < PLAYER_COMMENT+MAXSTREAM) { AddComment(p,No-PLAYER_COMMENT,(const tchar_t*)Data); return ERR_NONE; } switch (No) { case NODE_HIBERNATE: return Result; case NODE_CRASH: #ifdef MULTITHREAD p->RunProcess = 0; p->RunInput = 0; p->Lock = NULL; p->LockInput = NULL; p->LockProcess = NULL; p->EventProcess = NULL; p->EventRunInput = NULL; p->EventRunProcess = NULL; #ifndef NDEBUG p->ProcessLocked = 0; p->InputLocked = 0; #endif #endif return ERR_NONE; case PLAYER_NOT_SUPPORTED_DATA: // called by process thread assert(Size == sizeof(pin)); return AddCodecSkip(p,(const pin*)Data); case NODE_SETTINGSCHANGED: UpdateWnd(p,Context()->Wnd); UpdatePriority(p); UpdateEqualizer(p); UpdateSoftVolume(p); return ERR_NONE; case PLAYER_PERCENT: assert(Size == sizeof(fraction)); return InSeek(p,-1,(const fraction*)Data,1); case PLAYER_POSITION: assert(Size == sizeof(tick_t)); t = *(const tick_t*)Data; if (t<0) t=0; return InSeek(p,t,NULL,1); case PLAYER_BACKGROUND: SETVALUECMP(p->Background,bool_t,UpdateBackground(p),EqBool); return Result; case PLAYER_POWEROFF: SETVALUECMP(p->PowerOff,bool_t,UpdatePowerOff(p),EqBool); return Result; } Lock(p,0); if (No >= PLAYER_LIST_URL && No <= PLAYER_LIST_URL+p->PlayListCount) { No -= PLAYER_LIST_URL; if (No == p->PlayListCount) SetPlayListCount(p,No+1); if (No < p->PlayListCount) { if (!Size) Data = T(""); p->PlayList[No].Changed = 1; tcscpy_s(p->PlayList[No].URL,TSIZEOF(p->PlayList[No].URL),(tchar_t*)Data); NotifyList(p); Result = ERR_NONE; } } else if (No >= PLAYER_LIST_TITLE && No <= PLAYER_LIST_TITLE+p->PlayListCount) { No -= PLAYER_LIST_TITLE; if (No == p->PlayListCount) SetPlayListCount(p,No+1); if (No < p->PlayListCount) { if (!Size) Data = T(""); tcscpy_s(p->PlayList[No].Title,TSIZEOF(p->PlayList[No].Title),(tchar_t*)Data); NotifyList(p); Result = ERR_NONE; } } else if (No >= PLAYER_LIST_LENGTH && No <= PLAYER_LIST_LENGTH+p->PlayListCount) { No -= PLAYER_LIST_LENGTH; if (No == p->PlayListCount) SetPlayListCount(p,No+1); if (No < p->PlayListCount) { p->PlayList[No].Length = *(tick_t*)Data; NotifyList(p); Result = ERR_NONE; } } else switch (No) { case PLAYER_TIMER_LEFT: SETVALUE(p->TimerLeft,bool_t,ERR_NONE); break; case PLAYER_PLAY_SPEED: SETVALUE(p->PlaySpeed,fraction,UpdateSpeed(p)); break; case PLAYER_FFWD_SPEED: SETVALUE(p->FFwdSpeed,fraction,UpdateSpeed(p)); break; case PLAYER_REPEAT: SETVALUE(p->Repeat,bool_t,ERR_NONE); break; case PLAYER_PLAYATOPEN: SETVALUE(p->PlayAtOpen,bool_t,ERR_NONE); break; case PLAYER_PLAYATOPEN_FULL: SETVALUE(p->PlayAtOpenFull,bool_t,ERR_NONE); break; case PLAYER_EXIT_AT_END: SETVALUE(p->ExitAtEnd,bool_t,ERR_NONE); break; case PLAYER_KEEPPLAY_AUDIO: SETVALUE(p->KeepPlayAudio,bool_t,ERR_NONE); break; case PLAYER_KEEPPLAY_VIDEO: SETVALUE(p->KeepPlayVideo,bool_t,ERR_NONE); break; case PLAYER_SHOWINBACKGROUND: SETVALUE(p->ShowInBackground,bool_t,UpdateShowInBackground(p)); break; case PLAYER_SINGLECLICKFULLSCREEN: SETVALUE(p->SingleClickFullScreen,bool_t,ERR_NONE); break; case PLAYER_KEEPLIST: SETVALUE(p->KeepList,bool_t,ERR_NONE); break; case PLAYER_MOVEBACK_STEP: SETVALUE(p->MoveBack,tick_t,ERR_NONE); break; case PLAYER_MOVEFFWD_STEP: SETVALUE(p->MoveFFwd,tick_t,ERR_NONE); break; case PLAYER_SHUFFLE: SETVALUE(p->Shuffle,bool_t,RandomizePlayIndex(p,1)); break; case PLAYER_AUDIO_QUALITY: SETVALUECMP(p->AudioQuality,int,UpdateAudio(p),EqInt); break; case PLAYER_VIDEO_QUALITY: SETVALUE(p->VideoQuality,int,UpdateAllVideoQuality(p)); break; case PLAYER_FULLSCREEN: SETVALUECMP(p->FullScreen,bool_t,ERR_NONE,EqBool); break; case PLAYER_AUTOPREROTATE: SETVALUECMP(p->AutoPreRotate,bool_t,UpdateVideo(p,0),EqBool); break; case PLAYER_SKIN_VIEWPORT: SETVALUECMP(p->SkinViewport,rect,ERR_NONE,EqRect); break; case PLAYER_CLIPPING: SETVALUECMP(p->Clipping,bool_t,UpdateVideo(p,0),EqBool); break; case PLAYER_ASPECT: SETVALUECMP(p->Aspect,fraction,UpdateVideo(p,0),EqFrac); break; case PLAYER_STEREO: SETVALUECMP(p->Stereo,int,UpdateAudio(p),EqInt); break; case PLAYER_FULL_ZOOM: SETVALUECMP(p->FullZoom,fraction,UpdateVideo(p,0),EqFrac); break; case PLAYER_SKIN_ZOOM: SETVALUECMP(p->SkinZoom,fraction,UpdateVideo(p,0),EqFrac); break; case PLAYER_SMOOTH50: SETVALUECMP(p->SmoothZoom50,bool_t,UpdateVideo(p,0),EqBool); break; case PLAYER_SMOOTHALWAYS: SETVALUECMP(p->SmoothZoomAlways,bool_t,UpdateVideo(p,0),EqBool); break; case PLAYER_FULL_DIR: SETVALUECMP(p->FullDir,int,UpdateVideo(p,0),EqInt); break; case PLAYER_SKIN_DIR: SETVALUECMP(p->SkinDir,int,UpdateVideo(p,0),EqInt); break; case PLAYER_VIDEO_ACCEL: SETVALUE(p->VideoAccel,bool_t,ERR_NONE); break; case PLAYER_VSTREAM: SETVALUECMP(p->Selected[PACKET_VIDEO],int,UpdateStreamsMask(p),EqInt); break; case PLAYER_ASTREAM: SETVALUECMP(p->Selected[PACKET_AUDIO],int,UpdateStreamsMask(p),EqInt); break; case PLAYER_SUBSTREAM: SETVALUECMP(p->Selected[PACKET_SUBTITLE],int,UpdateStreamsMask(p),EqInt); break; case PLAYER_NOTIFY: SETVALUENULL(p->Notify,notify,ERR_NONE,p->Notify.Func=NULL); break; case PLAYER_LIST_NOTIFY: SETVALUENULL(p->ListNotify,notify,ERR_NONE,p->ListNotify.Func=NULL); break; case PLAYER_FOREGROUND: SETVALUECMP(p->Foreground,bool_t,UpdateForeground(p),EqBool); break; case PLAYER_INSEEK: SETVALUECMP(p->InSeek,bool_t,UpdateInSeek(p),EqBool); break; case PLAYER_MUTE: SETVALUECMP(p->Mute,bool_t,UpdateAudio(p),EqBool); break; case PLAYER_CURRENTDIR: SETSTRING(p->CurrentDir); break; case PLAYER_DISCARDLIST: SETVALUE(p->DiscardList,bool_t,ERR_NONE); break; case PLAYER_PREAMP: SETVALUECMP(p->PreAmp,int,UpdateAudio(p),EqInt); break; case PLAYER_PAN: SETVALUECMP(p->Pan,int,UpdateAudio(p),EqInt); break; case PLAYER_VOLUME: SETVALUECMP(p->Volume,int,UpdateVolume(p),EqInt); break; // need process locking for software win32 waveout pausing case PLAYER_UPDATEVIDEO: Result = UpdateVideo(p,0); break; case PLAYER_ROTATEEND: p->Rotating = 0; Result = UpdateVideo(p,0); break; case PLAYER_RESETVIDEO: if (p->VOutput) p->VOutput->Set(p->VOutput,VOUT_RESET,NULL,0); break; case PLAYER_ROTATEBEGIN: p->Rotating = 1; if (p->VOutput) { bool_t Visible = 0; p->VOutput->Set(p->VOutput,VOUT_VISIBLE,&Visible,sizeof(Visible)); } break; case PLAYER_VOUTPUTID: assert(Size==sizeof(int)); Old = p->VOutputId; New = *(int*)Data; if (New && !NodeEnumClass(NULL,New)) // old driver removed { New = p->VOutputIdMax; p->VideoAccel = VOutIDCT(New); } p->VOutputId = New; p->SkipAccelFrom = 0; if (!VOutIDCT(New)) p->VideoAccel = 0; UpdateStreams(p,Old==0,0); break; case PLAYER_AOUTPUTID: assert(Size==sizeof(int)); Old = p->AOutputId; New = *(int*)Data; if (New && !NodeEnumClass(NULL,New)) // old driver removed New = p->AOutputIdMax; p->AOutputId = New; UpdateStreams(p,Old==0,0); break; case PLAYER_UPDATEEQUALIZER: Result = UpdateEqualizer(p); break; case PLAYER_VOUTPUTID_MAX: assert(Size==sizeof(int)); if (p->VOutputIdMax != *(int*)Data && p->VOutputId && p->VOutputIdMax && p->VOutputId != p->VOutputIdMax) { // new highest priority video driver added. use it p->VOutputId = p->VOutputIdMax; p->VideoAccel = VOutIDCT(p->VOutputId); UpdateStreams(p,0,0); } Result = ERR_NONE; break; case PLAYER_AOUTPUTID_MAX: assert(Size==sizeof(int)); if (p->AOutputIdMax != *(int*)Data && p->AOutputId && p->AOutputIdMax && p->AOutputId != p->AOutputIdMax) { // new highest priority audio driver added. use it p->AOutputId = p->AOutputIdMax; UpdateStreams(p,0,0); } Result = ERR_NONE; break; case PLAYER_MOVEBACK: case PLAYER_MOVEFFWD: if (p->Format && p->Input) { int FilePos; int FileSize; tick_t Position = GetPosition(p,NULL); fraction Percent; if (Position >= 0) { if (No == PLAYER_MOVEBACK) Position -= p->MoveBack; else Position += p->MoveFFwd; if (Position<0) Position=0; Result = InSeek(p,Position,NULL,No == PLAYER_MOVEBACK); } else if (p->Input->Get(p->Input,STREAM_LENGTH,&FileSize,sizeof(FileSize))==ERR_NONE && p->Format->Get(p->Format,FORMAT_FILEPOS,&FilePos,sizeof(FilePos))==ERR_NONE) { if (No == PLAYER_MOVEBACK) FilePos -= FileSize/256; else FilePos += FileSize/256; Percent.Num = FilePos; Percent.Den = FileSize; if (Percent.Num<0) Percent.Num=0; Result = InSeek(p,-1,&Percent,No == PLAYER_MOVEBACK); } else Result = p->Player.Set(&p->Player,No == PLAYER_MOVEBACK?PLAYER_PREV:PLAYER_NEXT,NULL,0); } break; default: // inputthread locking needed for these Lock(p,1); switch (No) { case PLAYER_BUFFER_SIZE: assert(Size==sizeof(int)); p->BufferSize2 = (*(int*)Data *1024)/BLOCKSIZE; if (p->BufferSize2 < MINBUFFER/BLOCKSIZE) p->BufferSize2 = MINBUFFER/BLOCKSIZE; Result = UpdateFormat(p,1); break; case PLAYER_MD_BUFFER_SIZE: assert(Size==sizeof(int)); p->MDBufferSize2 = (*(int*)Data *1024)/BLOCKSIZE; if (p->MDBufferSize2 < MINBUFFER/BLOCKSIZE) p->MDBufferSize2 = MINBUFFER/BLOCKSIZE; Result = UpdateFormat(p,1); break; case PLAYER_BURSTSTART: assert(Size==sizeof(int)); p->BurstStart = (*(int*)Data *1024)/BLOCKSIZE; if (p->BurstStart < MINBUFFER/BLOCKSIZE) p->BurstStart = MINBUFFER/BLOCKSIZE; Result = ERR_NONE; break; case PLAYER_BENCHMARK: if (p->Format && p->Input) StartBench(p); break; case PLAYER_PLAY: if (Size == sizeof(bool_t)) { if (*(bool_t*)Data) { if (!p->Format && p->Wnd && p->PlayListCount) { if (Load(p,0,0,0) == ERR_FILE_NOT_FOUND) NextPrev(p,1,1,1); } if (!p->Format) { Result = ERR_INVALID_DATA; break; } p->Play = 1; p->FFwd = 0; } else { p->Play = 0; p->FFwd = 0; p->Fill = 1; } Result = SetPlay(p,0); } break; case PLAYER_FFWD: if (Size == sizeof(bool_t)) { if (*(bool_t*)Data) { if (!p->Format && p->Wnd && p->PlayListCount) Load(p,0,0,0); if (!p->Format) { Result = ERR_INVALID_DATA; break; } p->FFwd = 1; } else { p->FFwd = 0; if (!p->Play && !p->Bench) p->Fill = 1; } Result = SetPlay(p,0); } break; case PLAYER_LIST_COUNT: assert(Size == sizeof(int)); Result = SetPlayListCount(p,*(int*)Data); break; case PLAYER_LIST_CURRIDX: assert(Size == sizeof(int)); if (*(int*)DataPlayListCount && *(int*)Data>=0) Result = SetCurrent(p,p->PlayIndex[*(int*)Data],0); break; case PLAYER_LIST_CURRENT: assert(Size == sizeof(int)); Result = SetCurrent(p,*(int*)Data,1); break; case PLAYER_NEXT: t = FindChapter(p,1); if (t >= 0) Result = Seek2(p,t,NULL,1); else Result = NextPrev(p,1,0,0); break; case PLAYER_PREV: t = FindChapter(p,-1); if (t >= 0) Result = Seek2(p,t,NULL,1); else if (Size>0 && p->Timing && p->Position > TICKSPERSEC*2 && Seek2(p,0,NULL,0)==ERR_NONE) { ClearStats(p); Result = ERR_NONE; } else Result = NextPrev(p,-1,0,0); break; case PLAYER_STOP: SetPlay(p,1); if (!Size) { if (p->Streaming) Unload(p,0,0,1); else NextPrev(p,0,0,0); } break; case PLAYER_RESYNC: ReSync(p,1); Result = ERR_NONE; break; case PLAYER_MICRODRIVE: SETVALUE(p->MicroDrive,bool_t,UpdateFormat(p,1)); break; case PLAYER_UNDERRUN: SETVALUE(p->UnderRun,int,ERR_NONE); break; case PLAYER_AUDIO_UNDERRUN: SETVALUE(p->AudioUnderRun,int,ERR_NONE); break; } Unlock(p,1); break; } Unlock(p,0); return Result; } static bool_t CmdComment(player_base* p,int Stream,const tchar_t* Name,tchar_t* Value,int ValueLen) { bool_t Result = 0; const uint8_t *i; LockEnter(p->LockComment); for (i=ARRAYBEGIN(p->Comment,uint8_t);i!=ARRAYEND(p->Comment,uint8_t);i=CommentNext(i)) if (CommentStream(i)!=INVALID_COMMENT && (Stream<0 || CommentStream(i) == Stream) && tcsicmp(CommentName(i),Name)==0) { if (Result) { Result = 0; break; } if (Value) tcscpy_s(Value,ValueLen,CommentValue(i)); Result = 1; if (Stream>=0) break; } LockLeave(p->LockComment); return Result; } static void Paint(player_base* p,void* DC,int x0,int y0) { rect re; rect r; rect* Exclude = NULL; rgbval_t ColorKey; #ifdef MULTITHREAD assert(p->ProcessLocked != ThreadId() && p->InputLocked != ThreadId()); #endif LockEnter(p->Lock); if (p->Primary) { if (p->VOutput && p->VOutput->Get(p->VOutput,VOUT_OUTPUTRECT,&re,sizeof(re))==ERR_NONE && re.Width>0 && re.Height>0 && !p->TemporaryHidden) { re.x -= x0; re.y -= y0; if (p->VOutput->Get(p->VOutput,VOUT_COLORKEY,&ColorKey,sizeof(ColorKey)) == ERR_NONE) { if (ColorKey != p->ColorKey) { BrushDelete(p->ColorKeyBrush); p->ColorKey = ColorKey; p->ColorKeyBrush = BrushCreate(ColorKey); } WinFill(DC,&re,NULL,p->ColorKeyBrush); Exclude = &re; } else if (!p->Clipping && !p->Overlay) Exclude = &re; } r = p->Viewport; } else r = p->SkinViewport; LockLeave(p->Lock); r.x -= x0; r.y -= y0; WinFill(DC,&r,Exclude,p->BlackBrush); } static int Create(player_base* p) { int i; int Model; video Desktop; QueryDesktop(&Desktop); Model = QueryPlatform(PLATFORM_MODEL); // default values if (QueryPlatform(PLATFORM_TYPENO) == TYPE_SMARTPHONE) { p->UsedEnough2 = (256*1024)/BLOCKSIZE; p->BufferSize2 = (600*1024)/BLOCKSIZE; } else { p->UsedEnough2 = (512*1024)/BLOCKSIZE; p->BufferSize2 = (2400*1024)/BLOCKSIZE; #ifdef TARGET_WINCE if (QueryPlatform(PLATFORM_VER) < 421) p->BufferSize2 = (1200*1024)/BLOCKSIZE;; #endif #ifdef TARGET_PALMOS p->UsedEnough2 = (256*1024)/BLOCKSIZE; p->BufferSize2 = (2000*1024)/BLOCKSIZE; #endif } p->RndSeed = (uint32_t)(GetTimeTick()+GetTimeDate()); p->Color = NodeEnumObject(NULL,COLOR_ID); p->Equalizer = NodeEnumObject(NULL,EQUALIZER_ID); p->Player.Enum = (nodeenum)Enum; p->Player.Get = (nodeget)Get; p->Player.Set = (nodeset)Set; p->Player.Paint = (playerpaint)Paint; p->Player.CommentByName = (playercomment)CmdComment; p->Player.ListSwap = (playerswap)ListSwap; p->Player.Process = (playerprocess)ProcessThread; p->MDBufferSize2 = (16000*1024)/BLOCKSIZE; p->BurstStart = (1500*1024)/BLOCKSIZE; p->UnderRun = PERCENT_ONE*7/10; p->AudioUnderRun = 64; p->MoveFFwd = p->MoveBack = TICKSPERSEC*10; p->PlayAtOpen = 0; p->PlayAtOpenFull = 0; p->KeepList = 1; p->KeepPlayAudio = 1; #if defined(TARGET_WIN32) p->SingleClickFullScreen = 0; p->KeepPlayVideo = 1; p->ShowInBackground = 1; p->AutoPreRotate = 0; #else p->SingleClickFullScreen = 1; p->KeepPlayVideo = 0; p->ShowInBackground = 0; p->AutoPreRotate = Desktop.Width != Desktop.Height; #endif p->FullZoom.Den = 1; p->FullZoom.Num = 0; p->SkinZoom.Den = 1; p->SkinZoom.Num = 0; p->VideoQuality = 2; #if !defined(SH3) && !defined(MIPS) p->AudioQuality = 2; p->SmoothZoom50 = 1; p->SmoothZoomAlways = 0; #else p->AudioQuality = 1; p->SmoothZoom50 = 0; p->SmoothZoomAlways = 0; #if defined(SH3) p->FullZoom.Num = p->SkinZoom.Num = 1; #endif #endif #ifdef TARGET_SYMBIAN p->Volume = 50; #else p->Volume = 90; #endif p->Repeat = 1; #ifdef TARGET_WIN32 p->Aspect.Num = 0; #else p->Aspect.Num = 1; #ifdef ARM // use aspect with hardware scaler overlay drivers... if (NodeEnumClass(NULL,FOURCC('A','T','I','4')) || NodeEnumClass(NULL,FOURCC('G','E','2','D')) || NodeEnumClass(NULL,FOURCC('A','H','I','_')) || NodeEnumClass(NULL,FOURCC('2','7','0','G'))) p->Aspect.Num = 0; #endif #endif p->Aspect.Den = 1; p->PlaySpeed.Num = 1; p->PlaySpeed.Den = 1; p->FFwdSpeed.Num = 2; p->FFwdSpeed.Den = 1; p->SeekAfterSync.Num = -1; p->Primary = 1; p->SkinDir = -1; p->Foreground = 1; if (Model==MODEL_TUNGSTEN_T3) p->FullDir = -1; // special case because it can be slided into square format else { p->FullDir = GetOrientation(); if (Desktop.Width < Desktop.Height) { if (GetHandedness()) p->FullDir = CombineDir(p->FullDir,DIR_SWAPXY | DIR_MIRRORLEFTRIGHT,0); else p->FullDir = CombineDir(p->FullDir,DIR_SWAPXY | DIR_MIRRORUPDOWN,0); } } for (i=0;iSelected[i] = -1; p->CurrentChanged = 1; p->Position = -1; p->AOutputIdMax = p->AOutputId = NodeEnumClass(NULL,AOUT_CLASS); p->VOutputIdMax = p->VOutputId = NodeEnumClass(NULL,VOUT_CLASS); p->VideoAccel = VOutIDCT(p->VOutputId); #ifdef MULTITHREAD p->LockProcessCount = 0; p->LockInputCount = 0; p->LockComment = LockCreate(); p->Lock = LockCreate(); p->LockInput = LockCreate(); p->LockProcess = LockCreate(); p->EventProcess = EventCreate(0,0); p->EventRunProcess = EventCreate(1,0); p->EventRunInput = EventCreate(1,0); #endif p->ColorKey = RGB_NULL; p->ColorKeyBrush = NULL; p->BlackBrush = BrushCreate(0); UpdateFormat(p,0); UpdateRunProcess(p); UpdateRunInput(p); return ERR_NONE; } static void Delete(player_base* p) { UpdateWnd(p,NULL); #ifdef MULTITHREAD EventClose(p->EventRunInput); EventClose(p->EventRunProcess); EventClose(p->EventProcess); LockDelete(p->Lock); LockDelete(p->LockInput); LockDelete(p->LockProcess); LockDelete(p->LockComment); #endif BrushDelete(p->ColorKeyBrush); BrushDelete(p->BlackBrush); free(p->PlayList); free(p->PlayIndex); } void PlayerSaveList(player* Player,const tchar_t* Path,int Class) { playlist* Playlist = (playlist*)NodeCreate(Class); if (Playlist) { if (Playlist->WriteList) { stream* Input = GetStream(Path,0); if (Input) { bool_t b = 1; if (Input->Set(Input,STREAM_CREATE,&b,sizeof(b)) == ERR_NONE && Input->Set(Input,STREAM_URL,Path,sizeof(tchar_t)*(tcslen(Path)+1)) == ERR_NONE && Playlist->Set(Playlist,PLAYLIST_STREAM,&Input,sizeof(Input)) == ERR_NONE) { tchar_t Base[MAXPATH]; tchar_t Rel[MAXPATH]; tchar_t URL[MAXPATH]; tchar_t Title[256]; tick_t Length; int No,Count; SplitURL(Path,Base,TSIZEOF(Base),Base,TSIZEOF(Base),NULL,0,NULL,0); Player->Get(Player,PLAYER_LIST_COUNT,&Count,sizeof(Count)); for (No=0;NoGet(Player,PLAYER_LIST_URL+No,URL,sizeof(URL))==ERR_NONE) { RelPath(Rel,TSIZEOF(Rel),URL,Base); Title[0] = 0; Length = -1; Player->Get(Player,PLAYER_LIST_TITLE+No,Title,sizeof(Title)); Player->Get(Player,PLAYER_LIST_LENGTH+No,&Length,sizeof(Length)); Playlist->WriteList(Playlist,Rel,Title,Length); } } Playlist->WriteList(Playlist,NULL,NULL,0); Playlist->Set(Playlist,PLAYLIST_STREAM,NULL,0); } Input->Set(Input,STREAM_URL,NULL,0); NodeDelete((node*)Input); } } Playlist->Set(Playlist,PLAYLIST_STREAM,NULL,0); NodeDelete((node*)Playlist); } } int PlayerAddDir(player* Player,int Index, const tchar_t* Path, const tchar_t* Exts, bool_t ExtFilter, int Deep) { streamdir DirItem; stream* Stream; if (Deep < MAXDIRDEEP) // fail safe { Stream = GetStream(Path,1); if (Stream) { tchar_t Base[MAXPATH]; int Result = Stream->EnumDir(Stream,Path,Exts,ExtFilter,&DirItem); if (Result == ERR_NONE) { tcscpy_s(Base,TSIZEOF(Base),Path); Stream->Get(Stream,STREAM_BASE,Base,sizeof(Base)); } while (Result == ERR_NONE) { tchar_t PathItem[MAXPATH]; AbsPath(PathItem,TSIZEOF(PathItem),DirItem.FileName,Base); if (tcsncmp(PathItem,Base,tcslen(Base))==0) // don't go to other sites with http { if (DirItem.Size < 0) Index = PlayerAddDir(Player,Index,PathItem,Exts,ExtFilter,Deep+1); else Index = PlayerAdd(Player,Index,PathItem,DirItem.DisplayName); } Result = Stream->EnumDir(Stream,NULL,NULL,0,&DirItem); } NodeDelete((node*)Stream); } } return Index; } int PlayerAdd(player* Player,int Index, const tchar_t* Path, const tchar_t* Title) { stream* Input; array List; // process playlist files NodeEnumClassEx(&List,PLAYLIST_CLASS,NULL,Path,NULL,0); if (!ARRAYEMPTY(List) && (Input = GetStream(Path,0))!=NULL) { if (Input->Set(Input,STREAM_URL,Path,sizeof(tchar_t)*(tcslen(Path)+1)) == ERR_NONE && LoadPlaylist(Player,&List,Input,&Index,Path,NULL,0)) Path = NULL; Input->Set(Input,STREAM_URL,NULL,0); NodeDelete((node*)Input); } ArrayClear(&List); if (Path) { Player->Set(Player,PLAYER_LIST_URL+Index,Path,sizeof(tchar_t)*(tcslen(Path)+1)); Player->Set(Player,PLAYER_LIST_TITLE+Index,Title,Title?sizeof(tchar_t)*(tcslen(Title)+1):0); ++Index; } return Index; } const tchar_t* PlayerComment(int Code) { switch (Code) { case COMMENT_TITLE: return T("TITLE"); case COMMENT_ARTIST: return T("ARTIST"); case COMMENT_ALBUM: return T("ALBUM"); case COMMENT_GENRE: return T("GENRE"); case COMMENT_LANGUAGE: return T("LANGUAGE"); case COMMENT_AUTHOR: return T("AUTHOR"); case COMMENT_COPYRIGHT: return T("COPYRIGHT"); case COMMENT_PRIORITY: return T("PRIORITY"); case COMMENT_YEAR: return T("YEAR"); case COMMENT_TRACK: return T("TRACK"); case COMMENT_COMMENT: return T("COMMENT"); case COMMENT_COVER: return T("COVER"); case COMMENT_REDIRECT: return T("REDIRECT"); } return NULL; } tick_t PlayerGetChapter(player* p,int No,tchar_t* OutName,int OutNameLen) { tchar_t Chapter[64]; tchar_t s[64]; int Hour,Min,Sec,MSec=0; if (No>0) { stprintf_s(Chapter,TSIZEOF(Chapter),T("CHAPTER%02d"),No); if (p->CommentByName(p,-1,Chapter,s,TSIZEOF(s)) && stscanf(s,T("%d:%d:%d.%d"),&Hour,&Min,&Sec,&MSec)>=3) { if (OutName) { stprintf_s(Chapter,TSIZEOF(Chapter),T("CHAPTER%02dNAME"),No); if (!p->CommentByName(p,-1,Chapter,OutName,OutNameLen)) stprintf_s(OutName,OutNameLen,T("%d"),No); } return Scale(((Hour*60+Min)*60+Sec)*1000+MSec,TICKSPERSEC,1000); } } return -1; } bool_t PlayerGetStream(player* Player,int No,packetformat* OutFormat,tchar_t* OutName,int OutNameLen,int* OutPri) { tchar_t s[256]; node* Format; packetformat PacketFormat; int i; if (Player->Get(Player,PLAYER_FORMAT,&Format,sizeof(Format))==ERR_NONE && Format) { int Count[PACKET_MAX]; memset(Count,0,sizeof(Count)); for (i=0;Format->Get(Format,(FORMAT_STREAM+i)|PIN_FORMAT,&PacketFormat,sizeof(PacketFormat))==ERR_NONE;++i) { ++Count[PacketFormat.Type]; if (i==No) { if (OutPri) { if (Player->CommentByName(Player,i,PlayerComment(COMMENT_PRIORITY),s,TSIZEOF(s))) *OutPri = StringToInt(s,0); else *OutPri = 0; } if (OutName) { if (Player->CommentByName(Player,i,PlayerComment(COMMENT_LANGUAGE),s,TSIZEOF(s)) || Player->CommentByName(Player,i,PlayerComment(COMMENT_TITLE),s,TSIZEOF(s))) tcscpy_s(OutName,OutNameLen,s); else stprintf_s(OutName,OutNameLen,T("%s %d"),LangStr(PLAYER_ID,STREAM_NAME+PacketFormat.Type),Count[PacketFormat.Type]); } if (OutFormat) *OutFormat = PacketFormat; return 1; } } } return 0; } static const nodedef Player = { sizeof(player_base)|CF_GLOBAL|CF_SETTINGS, PLAYER_ID, NODE_CLASS, PRI_MAXIMUM+600, (nodecreate)Create, (nodedelete)Delete, }; typedef struct playerbuffer { node Node; node* Player; } playerbuffer; static int BufferEnum(playerbuffer* p, int* No, datadef* Param) { int Result = NodeEnumTable(No,Param,BufferParams); if (Result == ERR_NONE) Param->Name = LangStr(PLAYER_ID,Param->No); return Result; } static int BufferGet(playerbuffer* p, int No, void* Data, int Size) { return p->Player->Get(p->Player,No,Data,Size); } static int BufferSet(playerbuffer* p, int No, const void* Data, int Size) { return p->Player->Set(p->Player,No,Data,Size); } static int BufferCreate(playerbuffer* p) { p->Player = Context()->Player; p->Node.Enum = (nodeenum)BufferEnum; p->Node.Get = (nodeget)BufferGet; p->Node.Set = (nodeset)BufferSet; return ERR_NONE; } static const nodedef Buffer = { sizeof(playerbuffer)|CF_GLOBAL|CF_SETTINGS, PLAYER_BUFFER_ID, NODE_CLASS, PRI_MAXIMUM+60, (nodecreate)BufferCreate }; void Player_Init() { NodeRegisterClass(&Player); Context()->Player = NodeEnumObject(NULL,PLAYER_ID); NodeRegisterClass(&Buffer); } void Player_Done() { Context()->Player = NULL; NodeUnRegisterClass(PLAYER_ID); NodeUnRegisterClass(PLAYER_BUFFER_ID); }