4376 lines
109 KiB
C
4376 lines
109 KiB
C
|
/*****************************************************************************
|
||
|
*
|
||
|
* 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 (*No<p->PlayListCount*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;Index<p->PlayListCount;++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;No<p->PlayListCount;++No)
|
||
|
Index[No] = No;
|
||
|
|
||
|
if (p->Shuffle)
|
||
|
{
|
||
|
for (n=0;n<4;++n)
|
||
|
for (No=0;No<p->PlayListCount;++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;i<p->PlayListCount;++i)
|
||
|
if (Index[i]<Count)
|
||
|
Index[j++] = Index[i];
|
||
|
assert(j==Count);
|
||
|
}
|
||
|
|
||
|
// realloc index
|
||
|
Index = (int*)realloc(Index,sizeof(int)*Count);
|
||
|
if (!Index && Count)
|
||
|
{
|
||
|
SetPlayListCount(p,0);
|
||
|
return ERR_OUT_OF_MEMORY;
|
||
|
}
|
||
|
|
||
|
// add new items
|
||
|
for (i=p->PlayListCount;i<Count;++i)
|
||
|
{
|
||
|
memset(List+i,0,sizeof(item));
|
||
|
List[i].Length = -1;
|
||
|
List[i].Changed = 1;
|
||
|
Index[i] = i;
|
||
|
if (p->Shuffle)
|
||
|
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;i<p->CodecSkipCount;++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;i<PACKET_MAX;++i)
|
||
|
p->Selected[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;No<p->PlayListCount;++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;Tries<p->PlayListCount;++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.BufferUsedAfter<State.BufferUsedBefore)
|
||
|
{
|
||
|
EventSet(p->EventProcess); // 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.BufferUsedAfter<p->UsedEnough2 || p->Clipping || !p->TimerPlay)))
|
||
|
{
|
||
|
DEBUG_MSG(DEBUG_PLAYER,T("Process Sleep"));
|
||
|
ThreadSleep(0);
|
||
|
}
|
||
|
#else
|
||
|
if (p->WaitForProcess && State.BufferUsedAfter<State.BufferUsedBefore)
|
||
|
p->WaitForProcess = 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)<Limit;
|
||
|
}
|
||
|
|
||
|
static NOINLINE tick_t FindChapter(player_base* p,int Dir)
|
||
|
{
|
||
|
int No;
|
||
|
if (p->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;No<MAXCHAPTER;++No)
|
||
|
{
|
||
|
tick_t Chapter = PlayerGetChapter(&p->Player,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*)Data<p->PlayListCount && *(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;i<PACKET_MAX;++i)
|
||
|
p->Selected[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;No<Count;++No)
|
||
|
{
|
||
|
if (Player->Get(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);
|
||
|
}
|
||
|
|