SmartFarm/SOURCE/SmartFarmHMI/SmartFarmHMI_VCL_RTSP_/MPVBasePlayer.pas
2026-09-04 10:53:44 +09:00

1683 lines
47 KiB
ObjectPascal

unit MPVBasePlayer;
// MPV base player classes
// Author: Edward G. (nbuyer@gmail.com)
{.$DEFINE MPV_DYNAMIC_LOAD} // should define in project options "Conditional defines"
interface
uses
{$IFDEF MSWINDOWS}
Windows,
{$ENDIF}
SysUtils, Classes, SyncObjs, Variants,
MPVConst, MPVClient, MPVNode, MPVTrack;
const
DEF_MPV_EVENT_SECONDS = 0.5;
MPV_INVALID_SECOND = -10000000;
type
TMPVBasePlayer = class;
TMPVErrorCode = Integer;
TMPVException = class(Exception);
TMPVPlayerState = (mpsUnk, mpsLoading, mpsPlay, mpsStep, mpsPause, mpsStop, mpsEnd, mpsErr);
TMPVEventThread = class(TThread)
private
m_cPlayer: TMPVBasePlayer;
protected
procedure Execute; override;
public
constructor Create(cPlayer: TMPVBasePlayer);
end;
TMPVDestroyThread = class(TThread)
private
m_hMPV: PMPVHandle;
protected
procedure Execute; override;
public
constructor Create(hMPV: PMPVHandle);
end;
TMPVFileOpen = procedure (cSender: TObject; const sPath: string) of object;
// pData=PAnsiString/PDouble/PInt32/PInt64 depends on nFmt,
// use TMPVNode.LoadFromMPVNode() if it is a MPV_FORMAT_NODE
TMPVPropertyChangedEvent = procedure (cSender: TObject; nID: MPVUInt64;
nFmt: mpv_format; pData: Pointer) of object;
TMPVProgressEvent = procedure (cSender: TObject; fCurSec, fTotalSec: Double) of object;
TMPVErrorMessage = procedure (cSender: TObject; const sPrefix: string;
nLevel: Int32; const sMsg: string) of object;
TMPVStateChanged = procedure (cSender: TObject; eState: TMPVPlayerState) of object;
TMPVBasePlayer = class
private
m_cLock: SyncObjs.TCriticalSection; // Lock
m_cEventThrd: TMPVEventThread; // Thread to process events
m_fEventWait: Double; // Wait event seconds
m_eOnFileOpen: TMPVFileOpen;
m_eOnProgress: TMPVProgressEvent;
m_eOnPropChged: TMPVPropertyChangedEvent;
m_eOnErrMsg: TMPVErrorMessage;
m_eOnStateChged: TMPVStateChanged;
private
procedure SetATrack(const Value: string);
procedure SetSTrack(const Value: string);
procedure SetVTrack(const Value: string);
procedure SetCurSec(const Value: Double);
procedure SetSpeed(const Value: Double);
procedure SetVol(const Value: Double);
procedure SetVolMute(const Value: Boolean);
function GetAudioDev: string;
procedure SetAudioDev(const Value: string);
function GetAudioDevList: string;
function GetOnProgress: TMPVProgressEvent;
procedure SetOnProgress(const Value: TMPVProgressEvent);
function GetOnProgChg: TMPVPropertyChangedEvent;
procedure SetOnProgChg(const Value: TMPVPropertyChangedEvent);
function GetOnErrMsg: TMPVErrorMessage;
function GetOnFileOpen: TMPVFileOpen;
procedure SetOnErrMsg(const Value: TMPVErrorMessage);
procedure SetOnFileOpen(const Value: TMPVFileOpen);
function GetOnStateChg: TMPVStateChanged;
procedure SetOnStateChg(const Value: TMPVStateChanged);
procedure SetState(eNewState: TMPVPlayerState);
protected
m_hMPV: PMPVHandle; // MPV Handle
m_fLenInSec, m_fCurSec: Double; // Total / current seconds "time-pos"
m_fLenMax: Double; // Total-0.002, a var to check if reach max
m_fSpeed: Double; // Speed
m_fVol: Double; // Volume
m_bMute: Boolean; // Mute flag
m_nX, m_nY: Int64; // Video width/height
m_sAudioDev: string; // name: "auto"
m_sAudioDevList: string; // JSON array
m_sFileName: string; // Current file name
m_eState: TMPVPlayerState; // Current player state
m_cTrackList: TMPVTrackList; // All tracks' list
m_sCurVTrk, m_sCurATrk, m_sCurSTrk: string; // Current track IDs
protected
procedure MPVGetMem(var P: Pointer; nSize: Integer); inline;
procedure MPVFreeMem(P: Pointer); inline;
function HandleError(nCode: Integer; const sFunc: string;
bRaise: Boolean=False): TMPVErrorCode;
procedure FreePlayer; virtual;
procedure EventLoop(pbCancel: PBoolean); virtual;
// Override these handlers to handle diff MPV events, see MPV documents
function DoEventPropertyChange(nID: MPVUInt64;
pEP: P_mpv_event_property): TMPVErrorCode; virtual;
function DoEventFileLoaded: TMPVErrorCode; virtual;
function DoEventLogMsg(pLM: P_mpv_event_log_message): TMPVErrorCode; virtual;
function DoEventClientMsg(pCM: P_mpv_event_client_message): TMPVErrorCode; virtual;
function DoEventVideoReconfig: TMPVErrorCode; virtual;
function DoEventSeek: TMPVErrorCode; virtual;
function DoEventRestart: TMPVErrorCode; virtual;
function DoEventStartFile(pSF: P_mpv_event_start_file): TMPVErrorCode; virtual;
function DoEventEndFile(pEF: P_mpv_event_end_file): TMPVErrorCode; virtual;
function DoEventShutdown: TMPVErrorCode; virtual;
function DoEventGetPropertyReply(nErr: MPVInt; nID: MPVUInt64;
pEP: P_mpv_event_property): TMPVErrorCode; virtual;
function DoEventSetPropertyReply(nErr: MPVInt;
nID: MPVUInt64): TMPVErrorCode; virtual;
function DoEventCommandReply(nErr: MPVInt; nID: MPVUInt64;
pEC: P_mpv_event_command): TMPVErrorCode; virtual;
procedure DoSetVideoSize; virtual;
procedure OnStateChanged;
function ObserveProperty(const sName: string; nID: UInt64;
nFmt: MPVEnum = MPV_FORMAT_NODE): TMPVErrorCode;
function SetTrack(eType: TMPVTrackType; const sID, sPropName: string): TMPVErrorCode;
public
constructor Create;
destructor Destroy; override;
procedure Lock; inline;
procedure Unlock; inline;
procedure NotifyFree; virtual; // Only notify to free, no wait
// Initialize player, bind MPV with window handle
function InitPlayer(const sWinHandle, sScrShotDir, sConfigDir, sLogFile: string;
fEventWait: Double = DEF_MPV_EVENT_SECONDS): TMPVErrorCode; virtual;
// Override this to do things before/after MPV init()
procedure ProcessCmdLine(bBeforeInit: Boolean); virtual;
// Call your logger when needed
procedure Log(const sMsg: string; bError: Boolean); virtual;
// Send command(s) to MPV
function CommandStr(const sCmd: string): TMPVErrorCode;
function CommandList(cCmds: TStrings; nID: MPVUInt64 = 0): TMPVErrorCode;
function Command(const yCmds: array of string; nID: MPVUInt64 = 0): TMPVErrorCode;
// Get property from MPV
function GetPropertyBool(const sName: string; var Value: Boolean; bLogError: Boolean = True): TMPVErrorCode;
function SetPropertyBool(const sName: string; Value: Boolean; nID: MPVUInt64 = 0): TMPVErrorCode;
function GetPropertyInt64(const sName: string; var Value: Int64; bLogError: Boolean = True): TMPVErrorCode;
function SetPropertyInt64(const sName: string; Value: Int64; nID: MPVUInt64 = 0): TMPVErrorCode;
function GetPropertyDouble(const sName: string; var Value: Double; bLogError: Boolean = True): TMPVErrorCode;
function SetPropertyDouble(const sName: string; Value: Double; nID: MPVUInt64 = 0): TMPVErrorCode;
function GetPropertyString(const sName: string; var Value: string; bLogError: Boolean = True): TMPVErrorCode;
function SetPropertyString(const sName, sValue: string; nID: MPVUInt64 = 0): TMPVErrorCode;
function GetPropertyNode(const sName: string; cNode: TMPVNode; bLogError: Boolean = True): TMPVErrorCode;
// Observe property, set OnPropertyChanged to handle the change event
function ObservePropertyBool(const sName: string; nID: UInt64): TMPVErrorCode;
function ObservePropertyInt64(const sName: string; nID: UInt64): TMPVErrorCode;
function ObservePropertyDouble(const sName: string; nID: UInt64): TMPVErrorCode;
function ObservePropertyString(const sName: string; nID: UInt64): TMPVErrorCode;
function UnobserveProperty(nID: UInt64): TMPVErrorCode;
// Open file/URL to play
function OpenFile(const sFullName: string): TMPVErrorCode;
// Get MPV current state
function GetState: TMPVPlayerState; inline;
// Pause video
function Pause: TMPVErrorCode;
// Resume play
function Resume: TMPVErrorCode;
// Stop playing
function Stop: TMPVErrorCode;
// Seek to position(seconds)
function Seek(fPos: Double; bRelative: Boolean): TMPVErrorCode;
// Copy tracks' info filtered by eType(trkUnknown for all)
function CopyTrackInfoList(eType: TMPVTrackType; cList: TMPVTrackList): Integer;
// Set video track: sID = [id] or [title]
function SetVideoTrack(const sID: string): TMPVErrorCode;
// Set audio track: sID = [id] or [title]
function SetAudioTrack(const sID: string): TMPVErrorCode;
// Set subtitle: sID = [id] or [title]
function SetSubTitle(const sIDFile: string; bExternal: Boolean=False): TMPVErrorCode;
// Remove subtitle file
function RemoveSubTitle(const sFile: string): TMPVErrorCode;
// Get/set volume: 0~1000
function GetVolume: Double;
function SetVolume(fVol: Double): TMPVErrorCode;
function SetMute(bMute: Boolean): TMPVErrorCode;
function GetMute: Boolean;
// Audio delay, subtitle delay
function GetAudioDelay: Double;
procedure SetAudioDelay(fSec: Double);
function GetSubTitleDelay: Double;
procedure SetSubTitleDelay(fSec: Double);
public
// Player current status/information
property FileName: string read m_sFileName;
property CurrentVideoTrack: string read m_sCurVTrk write SetVTrack;
property CurrentAudioTrack: string read m_sCurATrk write SetATrack;
property CurrentSubtitle: string read m_sCurSTrk write SetSTrack;
property PlaybackSpeed: Double read m_fSpeed write SetSpeed;
property TotalSeconds: Double read m_fLenInSec;
// Current video progress, you can use a TTimer to display progress
property CurrentSeconds: Double read m_fCurSec write SetCurSec;
property VideoWidth: Int64 read m_nX;
property VideoHeight: Int64 read m_nY;
property Volume: Double read m_fVol write SetVol;
property Mute: Boolean read m_bMute write SetVolMute;
property AudioDevice: string read GetAudioDev write SetAudioDev;
property AudioDeviceList: string read GetAudioDevList;
// These events are called from another thread, be sure to use
// TThread.Synchronize() if you want to update UI.
property OnFileOpen: TMPVFileOpen read GetOnFileOpen write SetOnFileOpen;
property OnErrorMessage: TMPVErrorMessage read GetOnErrMsg write SetOnErrMsg;
property OnProgress: TMPVProgressEvent read GetOnProgress write SetOnProgress;
property OnPropertyChanged: TMPVPropertyChangedEvent read GetOnProgChg write SetOnProgChg;
property OnStateChged: TMPVStateChanged read GetOnStateChg write SetOnStateChg;
end;
function MPVLibLoaded(const sLibPath: string): Boolean;
implementation
{ TMPVEventThread }
constructor TMPVEventThread.Create(cPlayer: TMPVBasePlayer);
begin
m_cPlayer := cPlayer;
inherited Create(False);
end;
procedure TMPVEventThread.Execute;
begin
while not Terminated do
begin
m_cPlayer.EventLoop(@Terminated);
end;
end;
{ TMPVDestroyThread }
constructor TMPVDestroyThread.Create(hMPV: PMPVHandle);
begin
m_hMPV := hMPV;
inherited Create(False);
FreeOnTerminate := True;
end;
procedure TMPVDestroyThread.Execute;
begin
if m_hMPV<>nil then mpv_terminate_destroy(m_hMPV);
end;
{ TMPVBasePlayer }
function TMPVBasePlayer.Command(const yCmds: array of string; nID: MPVUInt64): TMPVErrorCode;
var
cStr: TStringList;
i: Integer;
begin
cStr := TStringList.Create;
try
for i := Low(yCmds) to High(yCmds) do
cStr.Add(yCmds[i]);
Result := CommandList(cStr, nID);
finally
cStr.Free;
end;
end;
function TMPVBasePlayer.CommandList(cCmds: TStrings; nID: MPVUInt64): TMPVErrorCode;
var
args, ppc: PPMPVChar;
P: Pointer;
s1: AnsiString;
i, n: Integer;
begin
if m_hMPV=nil then
begin
Result := MPV_ERROR_UNINITIALIZED;
Exit;
end;
if (cCmds=nil) or (cCmds.Count<=0) then
begin
Result := MPV_ERROR_INVALID_PARAMETER;
Exit;
end;
Result := MPV_ERROR_SUCCESS;
// Construct args
i := (cCmds.Count+1)*sizeof(PMPVChar);
args := nil;
MPVGetMem(Pointer(args), i);
FillChar(args^, i, 0);
ppc := args;
for i := 0 to cCmds.Count-1 do
begin
s1 := UTF8Encode(cCmds[i]);
n := Length(s1)+1; // #0 ended
if n>1 then
begin
// Alloc memory to hold one line
P := nil;
MPVGetMem(P, n*sizeof(s1[1]));
if P<>nil then
begin
Move(PAnsiChar(s1)^, P^, n*sizeof(s1[1]));
ppc^ := P;
Inc(ppc);
end else
begin
Result := MPV_ERROR_NOMEM;
Break;
end;
end;
end;
if Result=MPV_ERROR_SUCCESS then
begin
if nID>0 then
Result := HandleError(mpv_command_async(m_hMPV, nID, args), 'mpv_command_async')
else
Result := HandleError(mpv_command(m_hMPV, args), 'mpv_command');
end;
// free memory
ppc := args;
for i := 0 to cCmds.Count-1 do
begin
if ppc<>nil then
MPVFreeMem(ppc^);
Inc(ppc);
end;
MPVFreeMem(args);
end;
function TMPVBasePlayer.CommandStr(const sCmd: string): TMPVErrorCode;
begin
if m_hMPV=nil then
begin
Result := MPV_ERROR_UNINITIALIZED;
Exit;
end;
Result := HandleError(mpv_command_string(m_hMPV, PMPVChar(UTF8Encode(sCmd))),
'mpv_command_string');
end;
function TMPVBasePlayer.CopyTrackInfoList(eType: TMPVTrackType;
cList: TMPVTrackList): Integer;
begin
m_cLock.Enter;
cList.Assign(m_cTrackList, eType);
m_cLock.Leave;
Result := cList.Count;
end;
constructor TMPVBasePlayer.Create;
begin
m_cLock := SyncObjs.TCriticalSection.Create;
m_cTrackList := TMPVTrackList.Create();
m_eState := mpsUnk;
inherited Create;
end;
destructor TMPVBasePlayer.Destroy;
begin
FreePlayer;
FreeAndNil(m_cTrackList);
FreeAndNil(m_cLock);
inherited;
end;
function GetEventStr(nID: MPVEnum): string;
begin
case nID of
MPV_EVENT_NONE: Result := 'None';
MPV_EVENT_SHUTDOWN: Result := 'Shutdown';
MPV_EVENT_LOG_MESSAGE_: Result := 'LogMsg';
MPV_EVENT_GET_PROPERTY_REPLY: Result := 'GetPropReply';
MPV_EVENT_SET_PROPERTY_REPLY: Result := 'SetPropReply';
MPV_EVENT_COMMAND_REPLY: Result := 'CmdReply';
MPV_EVENT_START_FILE_: Result := 'StartFile';
MPV_EVENT_END_FILE_: Result := 'EndFile';
MPV_EVENT_FILE_LOADED: Result := 'FileLoaded';
MPV_EVENT_IDLE: Result := 'Idle';
MPV_EVENT_TICK: Result := 'Tick';
MPV_EVENT_CLIENT_MESSAGE_: Result := 'ClientMsg';
MPV_EVENT_VIDEO_RECONFIG: Result := 'VideoRecfg';
MPV_EVENT_AUDIO_RECONFIG: Result := 'AudioRecfg';
MPV_EVENT_SEEK: Result := 'Seek';
MPV_EVENT_PLAYBACK_RESTART: Result := 'PlayRestart';
MPV_EVENT_PROPERTY_CHANGE: Result := 'PropChg';
MPV_EVENT_QUEUE_OVERFLOW: Result := 'QueOverflow';
MPV_EVENT_HOOK_: Result := 'Hook';
else Result := IntToStr(nID);
end;
end;
procedure TMPVBasePlayer.EventLoop(pbCancel: PBoolean);
var
pe: P_mpv_event;
bChg: Boolean;
begin
if m_hMPV=nil then Exit;
while not pbCancel^ do
begin
pe := mpv_wait_event(m_hMPV, m_fEventWait); // seconds
if pe<>nil then
begin
case pe^.event_id of
MPV_EVENT_NONE: Continue;
MPV_EVENT_PROPERTY_CHANGE, MPV_EVENT_SEEK, MPV_EVENT_PLAYBACK_RESTART,
MPV_EVENT_LOG_MESSAGE_: ; // Too many
else
begin
Log(Format('event=%s; err=%d; ud=%d', [GetEventStr(pe^.event_id),
pe^.error, pe^.reply_userdata]), False);
end;
end;
case pe^.event_id of
MPV_EVENT_SEEK:
begin
// Update progress!
HandleError(DoEventSeek, 'DoEventSeek');
end;
MPV_EVENT_PROPERTY_CHANGE:
begin
HandleError(DoEventPropertyChange(pe^.reply_userdata,
P_mpv_event_property(pe^.data)), 'DoEventPropertyChange');
end;
MPV_EVENT_LOG_MESSAGE_:
begin
HandleError(DoEventLogMsg(P_mpv_event_log_message(pe^.data)), 'DoEventLogMsg');
end;
MPV_EVENT_VIDEO_RECONFIG:
begin
// video changed, resize window!
HandleError(DoEventVideoReconfig, 'DoEventVideoReconfig');
end;
MPV_EVENT_PLAYBACK_RESTART:
begin
// Start of playback or after seeking
HandleError(DoEventRestart, 'DoEventRestart');
end;
MPV_EVENT_FILE_LOADED:
begin
SetState(mpsPlay);
// start playback!
HandleError(DoEventFileLoaded, 'DoEventFileLoaded');
end;
MPV_EVENT_START_FILE_:
begin
// before load file
HandleError(DoEventStartFile(P_mpv_event_start_file(pe^.data)), 'DoEventStartFile');
end;
MPV_EVENT_END_FILE_:
begin
bChg := False;
m_cLock.Enter;
case m_eState of
mpsLoading, // called after open new file?
mpsEnd: ;
else
begin
bChg := True;
end;
end;
m_cLock.Leave;
if bChg then SetState(mpsEnd);
// after file unloaded
HandleError(DoEventEndFile(P_mpv_event_end_file(pe^.data)), 'DoEventEndFile');
end;
MPV_EVENT_SHUTDOWN:
begin
SetState(mpsEnd);
HandleError(DoEventShutdown, 'DoEventShutdown');
end;
MPV_EVENT_CLIENT_MESSAGE_:
begin
HandleError(DoEventClientMsg(P_mpv_event_client_message(pe^.data)),
'DoEventClientMsg');
end;
MPV_EVENT_GET_PROPERTY_REPLY:
begin
HandleError(DoEventGetPropertyReply(pe^.error, pe^.reply_userdata,
P_mpv_event_property(pe^.data)), 'DoEventGetPropertyReply');
end;
MPV_EVENT_SET_PROPERTY_REPLY:
begin
HandleError(DoEventSetPropertyReply(pe^.error, pe^.reply_userdata),
'DoEventSetPropertyReply');
end;
MPV_EVENT_COMMAND_REPLY:
begin
HandleError(DoEventCommandReply(pe^.error, pe^.reply_userdata,
P_mpv_event_command(pe^.data)), 'DoEventCommandReply');
end;
// MPV_EVENT_AUDIO_RECONFIG, MPV_EVENT_IDLE: ;
// MPV_EVENT_QUEUE_OVERFLOW: ;
// MPV_EVENT_HOOK_: ;
end;
end;
end;
end;
function TMPVBasePlayer.DoEventCommandReply(nErr: MPVInt; nID: MPVUInt64;
pEC: P_mpv_event_command): TMPVErrorCode;
begin
Result := MPV_ERROR_SUCCESS;
end;
function TMPVBasePlayer.DoEventEndFile(pEF: P_mpv_event_end_file): TMPVErrorCode;
begin
Result := MPV_ERROR_SUCCESS;
end;
function TMPVBasePlayer.DoEventFileLoaded: TMPVErrorCode;
var
eOpen: TMPVFileOpen;
begin
m_fSpeed := 1.0;
m_fLenInSec := -1;
m_fLenMax := 0;
GetPropertyDouble(STR_DURATION, m_fLenInSec);
m_sFileName := '';
GetPropertyString(STR_PATH, m_sFileName);
// TODO: "://" => get 'media-title'
GetPropertyInt64(STR_WIDTH, m_nX);
GetPropertyInt64(STR_HEIGHT, m_nY);
DoSetVideoSize;
m_cLock.Enter;
eOpen := m_eOnFileOpen;
m_cLock.Leave;
if Assigned(eOpen) then
try
eOpen(Self, m_sFileName);
except
end;
Result := MPV_ERROR_SUCCESS;
end;
function TMPVBasePlayer.DoEventGetPropertyReply(nErr: MPVInt; nID: MPVUInt64;
pEP: P_mpv_event_property): TMPVErrorCode;
begin
Result := MPV_ERROR_SUCCESS;
end;
function TMPVBasePlayer.DoEventLogMsg(pLM: P_mpv_event_log_message): TMPVErrorCode;
var
sPF, sLvl, sMsg: string;
eOnErr: TMPVErrorMessage;
begin
sLvl := string((pLM^.level)); // UTF8ToString
if (Length(pLM^.text)>1) and (sLvl<>'warn') then // $0a
begin
sPF := string(pLM^.prefix);
sMsg := string(UTF8ToString(pLM^.text));
Log(Format('MPV: prefix=%s, loglevel=%d, level=%s, msg=%s', [sPF,
pLM^.log_level, sLvl, sMsg]), False);
if (sLvl='error') then
begin
m_cLock.Enter;
eOnErr := m_eOnErrMsg;
m_cLock.Leave;
if Assigned(eOnErr) then
try
eOnErr(Self, sPF, pLM^.log_level, sMsg);
except
end;
end;
end;
Result := MPV_ERROR_SUCCESS;
end;
function TMPVBasePlayer.DoEventClientMsg(pCM: P_mpv_event_client_message): TMPVErrorCode;
var
i: Integer;
ppc: PPMPVChar;
begin
if pCM^.num_args>0 then
begin
ppc := pCM^.args;
for i := 0 to pCM^.num_args-1 do
begin
Log(Format('MPV clientmsg[%d]=%s', [i, UTF8ToString(ppc^)]), False);
Inc(ppc);
end;
end;
Result := MPV_ERROR_SUCCESS;
end;
function TMPVBasePlayer.DoEventPropertyChange(nID: MPVUInt64;
pEP: P_mpv_event_property): TMPVErrorCode;
var
s: string;
cNode: TMPVNode;
p: Pointer;
eOnPropChg: TMPVPropertyChangedEvent;
eOnProg: TMPVProgressEvent;
eState: TMPVPlayerState;
begin
Result := MPV_ERROR_SUCCESS;
case nID of
ID_PLAY_TIME:
begin
m_cLock.Enter;
case pEP^.format of
MPV_FORMAT_DOUBLE:
begin
m_fCurSec := PDouble(pEP^.data)^;
end;
MPV_FORMAT_NONE:
begin
m_fCurSec := 0;
end;
end;
eState := m_eState;
if (eState=mpsPlay) then
begin
if (m_fCurSec>=m_fLenMax) then
begin
// MPV does not notify EOF before unloading file
// although reached the end, but player is still "playing"
eState := mpsEnd;
end;
end else
begin
// playtime changed, not end pos, set to playing state if was ended
case eState of
mpsEnd:
begin
if (m_fCurSec<m_fLenMax-0.1) then eState := mpsPlay;
end;
end;
end;
eOnProg := m_eOnProgress;
m_cLock.Leave;
if Assigned(eOnProg) then
try
eOnProg(Self, m_fCurSec, m_fLenInSec);
except
end;
SetState(eState);
end;
ID_PAUSE:
begin
m_cLock.Enter;
eState := m_eState;
m_cLock.Leave;
if PMPVFlag(pEP^.data)^=0 then
begin
if eState=mpsPause then eState := mpsPlay; // change only when paused
end else
begin
eState := mpsPause;
end;
SetState(eState);
end;
ID_VOLUME:
begin
m_cLock.Enter;
case pEP^.format of
MPV_FORMAT_DOUBLE:
m_fVol := PDouble(pEP^.data)^; // Normally 100
MPV_FORMAT_NONE:
m_fVol := 0;
end;
m_cLock.Leave;
end;
ID_DURATION:
begin
m_cLock.Enter;
case pEP^.format of
MPV_FORMAT_DOUBLE:
begin
m_fLenInSec := PDouble(pEP^.data)^;
m_fLenMax := m_fLenInSec-0.05;
end;
MPV_FORMAT_NONE:
begin
m_fLenInSec := 0;
m_fLenMax := 0;
end;
end;
m_cLock.Leave;
end;
ID_MUTE:
begin
m_cLock.Enter;
if PMPVFlag(pEP^.data)^=0 then
begin
m_bMute := False;
end else
m_bMute := True;
m_cLock.Leave;
end;
ID_SID:
begin
s := VarToStr(GetMPVPropertyValue(pEP^.format, pEP^.data));
m_cLock.Enter;
m_sCurSTrk := s;
m_cLock.Leave;
end;
ID_AID:
begin
s := VarToStr(GetMPVPropertyValue(pEP^.format, pEP^.data));
m_cLock.Enter;
m_sCurATrk := s;
m_cLock.Leave;
end;
ID_VID:
begin
s := VarToStr(GetMPVPropertyValue(pEP^.format, pEP^.data));
m_cLock.Enter;
m_sCurVTrk := s;
m_cLock.Leave;
end;
ID_SPEED:
begin
m_cLock.Enter;
case pEP^.format of
MPV_FORMAT_DOUBLE:
m_fSpeed := PDouble(pEP^.data)^;
MPV_FORMAT_NONE:
m_fSpeed := 0;
end;
m_cLock.Leave;
end;
ID_TRACK_LIST:
begin
if pEP^.format=MPV_FORMAT_NODE then
begin
cNode := TMPVNode.Create;
cNode.LoadFromMPVNode(pEP^.data, False);
//cNode.SaveToFile('.\trklist.txt');
m_cLock.Enter;
try
m_cTrackList.LoadFromNode(cNode);
except
end;
m_cLock.Leave;
cNode.Free;
end;
end;
ID_AUDIO_DEV:
begin
m_cLock.Enter;
m_sAudioDev := VarToStr(GetMPVPropertyValue(pEP^.format, pEP^.data));
m_cLock.Leave;
end;
ID_AUDIO_DEV_LIST:
begin
m_cLock.Enter;
m_sAudioDevList := VarToStr(GetMPVPropertyValue(pEP^.format, pEP^.data));
m_cLock.Leave;
//'[{"name":"auto","description":"Autoselect device"},
//{"name":"wasapi/{g-u-i-d-e9a4855ad585}",
// "description":"Speakers (Realtek High Definition Audio(SST))"}]'
end;
end;
m_cLock.Enter;
eOnPropChg := m_eOnPropChged;
m_cLock.Leave;
if Assigned(eOnPropChg) then
begin
p := pEP^.data;
if pEP^.format=MPV_FORMAT_STRING then
begin
s := string(UTF8ToString(PPMPVChar(pEP^.data)^));
p := PAnsiChar(AnsiString(s));
end;
try
eOnPropChg(Self, nID, pEP^.format, p);
except
end;
end;
end;
function TMPVBasePlayer.DoEventRestart: TMPVErrorCode;
begin
Result := MPV_ERROR_SUCCESS;
end;
function TMPVBasePlayer.DoEventSeek: TMPVErrorCode;
begin
Result := MPV_ERROR_SUCCESS;
end;
function TMPVBasePlayer.DoEventSetPropertyReply(nErr: MPVInt;
nID: MPVUInt64): TMPVErrorCode;
begin
Result := MPV_ERROR_SUCCESS;
end;
function TMPVBasePlayer.DoEventShutdown: TMPVErrorCode;
begin
Result := MPV_ERROR_SUCCESS;
end;
function TMPVBasePlayer.DoEventStartFile(pSF: P_mpv_event_start_file): TMPVErrorCode;
begin
Result := MPV_ERROR_SUCCESS;
end;
function TMPVBasePlayer.DoEventVideoReconfig: TMPVErrorCode;
begin
// Could be error at the beginning
if GetPropertyInt64(STR_DWIDTH, m_nX, False)=MPV_ERROR_SUCCESS then
begin
if GetPropertyInt64(STR_DHEIGHT, m_nY)=MPV_ERROR_SUCCESS then
DoSetVideoSize;
end;
Result := MPV_ERROR_SUCCESS;
end;
procedure TMPVBasePlayer.DoSetVideoSize;
begin
// NULL
end;
procedure TMPVBasePlayer.FreePlayer;
begin
if m_cEventThrd<>nil then
begin
m_cEventThrd.Terminate;
m_cEventThrd.WaitFor; // may block if call after mpv_destroy()
FreeAndNil(m_cEventThrd);
end;
if m_hMPV<>nil then
begin
// This call might cause very long time when debugging in Delphi,
// but pretty fast when running alone.
mpv_terminate_destroy(m_hMPV); //mpv_destroy(m_hMPV);
//TMPVDestroyThread.Create(m_hMPV);
m_hMPV := nil;
end;
end;
function TMPVBasePlayer.GetAudioDelay: Double;
var
dbl: Double;
begin
dbl := 0;
if GetPropertyDouble(STR_AUDIO_DELAY, dbl)=MPV_ERROR_SUCCESS then
Result := dbl
else
Result := MPV_INVALID_SECOND;
end;
function TMPVBasePlayer.GetAudioDev: string;
begin
m_cLock.Enter;
Result := m_sAudioDev;
m_cLock.Leave;
end;
function TMPVBasePlayer.GetAudioDevList: string;
begin
m_cLock.Enter;
Result := m_sAudioDevList;
m_cLock.Leave;
end;
function TMPVBasePlayer.GetMute: Boolean;
begin
m_cLock.Enter;
Result := m_bMute;
m_cLock.Leave;
end;
function TMPVBasePlayer.GetOnErrMsg: TMPVErrorMessage;
begin
m_cLock.Enter;
Result := m_eOnErrMsg;
m_cLock.Leave;
end;
function TMPVBasePlayer.GetOnFileOpen: TMPVFileOpen;
begin
m_cLock.Enter;
Result := m_eOnFileOpen;
m_cLock.Leave;
end;
function TMPVBasePlayer.GetOnProgChg: TMPVPropertyChangedEvent;
begin
m_cLock.Enter;
Result := m_eOnPropChged;
m_cLock.Leave;
end;
function TMPVBasePlayer.GetOnProgress: TMPVProgressEvent;
begin
m_cLock.Enter;
Result := m_eOnProgress;
m_cLock.Leave;
end;
function TMPVBasePlayer.GetOnStateChg: TMPVStateChanged;
begin
m_cLock.Enter;
Result := m_eOnStateChged;
m_cLock.Leave;
end;
function TMPVBasePlayer.GetPropertyBool(const sName: string;
var Value: Boolean; bLogError: Boolean): TMPVErrorCode;
var
sNm: UTF8String;
n: MPVInt;
begin
if m_hMPV=nil then
begin
Result := MPV_ERROR_UNINITIALIZED;
Exit;
end;
sNm := UTF8Encode(sName);
n := 0;
Result := mpv_get_property(m_hMPV, PMPVChar(sNm),
MPV_FORMAT_FLAG, @n);
if Result<>MPV_ERROR_SUCCESS then
begin
if bLogError then
HandleError(Result, 'mpv_get_property(bool):'+sName);
end else
begin
Value := n<>0;
end;
end;
function TMPVBasePlayer.GetPropertyDouble(const sName: string;
var Value: Double; bLogError: Boolean): TMPVErrorCode;
var
sNm: UTF8String;
begin
if m_hMPV=nil then
begin
Result := MPV_ERROR_UNINITIALIZED;
Exit;
end;
sNm := UTF8Encode(sName);
Result := mpv_get_property(m_hMPV, PMPVChar(sNm),
MPV_FORMAT_DOUBLE, @Value);
if Result<>MPV_ERROR_SUCCESS then
begin
if bLogError then
HandleError(Result, 'mpv_get_property(dbl):'+sName);
end;
end;
function TMPVBasePlayer.GetPropertyInt64(const sName: string;
var Value: Int64; bLogError: Boolean): TMPVErrorCode;
var
sNm: UTF8String;
begin
if m_hMPV=nil then
begin
Result := MPV_ERROR_UNINITIALIZED;
Exit;
end;
sNm := UTF8Encode(sName);
Result := mpv_get_property(m_hMPV, PMPVChar(sNm),
MPV_FORMAT_INT64, @Value);
if Result<>MPV_ERROR_SUCCESS then
begin
if bLogError then
HandleError(Result, 'mpv_get_property(i64):'+sName);
end;
end;
function TMPVBasePlayer.GetPropertyNode(const sName: string;
cNode: TMPVNode; bLogError: Boolean): TMPVErrorCode;
var
sNm: UTF8String;
P: P_mpv_node;
begin
if m_hMPV=nil then
begin
Result := MPV_ERROR_UNINITIALIZED;
Exit;
end;
sNm := UTF8Encode(sName);
P := nil;
Result := mpv_get_property(m_hMPV, PMPVChar(sNm),
MPV_FORMAT_NODE, @P);
if Result<>MPV_ERROR_SUCCESS then
begin
if bLogError then
HandleError(Result, 'mpv_get_property(node):'+sName);
end;
if P<>nil then
begin
// Get value and free
cNode.LoadFromMPVNode(P, True);
end;
end;
function TMPVBasePlayer.GetPropertyString(const sName: string;
var Value: string; bLogError: Boolean): TMPVErrorCode;
var
sNm: UTF8String;
P: PMPVChar;
begin
if m_hMPV=nil then
begin
Result := MPV_ERROR_UNINITIALIZED;
Exit;
end;
sNm := UTF8Encode(sName);
P := nil;
Result := mpv_get_property(m_hMPV, PMPVChar(sNm),
MPV_FORMAT_STRING, @P);
if Result<>MPV_ERROR_SUCCESS then
begin
if bLogError then
HandleError(Result, 'mpv_get_property(str):'+sName);
end;
if P<>nil then
begin
// Get value and free
Value := string(UTF8ToString(P));
mpv_free(P);
end;
end;
function TMPVBasePlayer.GetState: TMPVPlayerState;
begin
m_cLock.Enter;
Result := m_eState;
m_cLock.Leave;
end;
function TMPVBasePlayer.GetSubTitleDelay: Double;
var
dbl: Double;
begin
dbl := 0;
if GetPropertyDouble(STR_SUB_DELAY, dbl)=MPV_ERROR_SUCCESS then
Result := dbl
else
Result := MPV_INVALID_SECOND;
end;
function TMPVBasePlayer.GetVolume: Double;
var
dbl: Double;
begin
dbl := 0;
if GetPropertyDouble(STR_VOLUME, dbl)=MPV_ERROR_SUCCESS then
Result := dbl
else
Result := -1;
end;
function TMPVBasePlayer.Seek(fPos: Double; bRelative: Boolean): TMPVErrorCode;
var
sAbs: string;
begin
if bRelative then sAbs := 'relative' else sAbs := 'absolute';
Result := Command([CMD_SEEK, FloatToStr(fPos), sAbs]);
// update current position
GetPropertyDouble(STR_TIME_POS, m_fCurSec, False);
end;
function TMPVBasePlayer.InitPlayer(const sWinHandle, sScrShotDir, sConfigDir, sLogFile: string;
fEventWait: Double): TMPVErrorCode;
begin
if not MPVLibLoaded('') then
begin
Result := MPV_ERROR_LOADING_FAILED;
Exit;
end;
FreePlayer();
// Basic procedure copied from MPV.NET
m_hMPV := mpv_create();
if m_hMPV=nil then
begin
Result := MPV_ERROR_NOMEM;
Exit;
end;
mpv_request_log_messages(m_hMPV, 'terminal-default');
{$IFDEF CONSOLE}
SetPropertyString('terminal', 'yes');
SetPropertyString('input-terminal', 'yes');
SetPropertyString('msg-level', 'osd/libass=fatal');
{$ENDIF}
//SetPropertyString('watch-later-options', STR_MUTE+','+STR_SID+','+STR_AID);
SetPropertyString('screenshot-directory', sScrShotDir);
// SetPropertyInt64('osd-duration', 2000);
// SetPropertyString('osd-playing-msg', '${filename}');
if sLogFile<>'' then SetPropertyString(STR_LOG_FILE, sLogFile);
SetPropertyString(STR_WID, sWinHandle);
SetPropertyString('osc', 'yes'); // On Screen Control
SetPropertyString('force-window', 'yes');
SetPropertyString('config-dir', sConfigDir); // mpv.conf location
SetPropertyString('config', 'yes');
SetPropertyBool('keep-open', True);
SetPropertyBool('keep-open-pause', False);
SetPropertyBool('input-default-bindings', True);
SetPropertyBool('input-builtin-bindings', False);
SetPropertyString('reset-on-next-file', 'speed,video-aspect-override,af,sub-visibility,audio-delay,pause');
ProcessCmdLine(True);
Result := HandleError(mpv_initialize(m_hMPV), 'mpv_initialize');
if Result<>MPV_ERROR_SUCCESS then Exit;
m_fEventWait := fEventWait;
m_cEventThrd := TMPVEventThread.Create(Self);
ObservePropertyBool(STR_PAUSE, ID_PAUSE);
ObservePropertyBool(STR_MUTE, ID_MUTE);
ObservePropertyInt64(STR_SID, ID_SID);
ObservePropertyInt64(STR_AID, ID_AID);
ObservePropertyInt64(STR_VID, ID_VID);
ObservePropertyDouble(STR_DURATION, ID_DURATION);
ObservePropertyDouble(STR_PLAY_TIME, ID_PLAY_TIME);
ObservePropertyDouble(STR_SPEED, ID_SPEED);
ObservePropertyDouble(STR_VOLUME, ID_VOLUME);
ObserveProperty(STR_TRACK_LIST, ID_TRACK_LIST); // Node
ObservePropertyString(STR_AUDIO_DEV, ID_AUDIO_DEV);
//ObservePropertyString(STR_AUDIO_DEV_LIST, ID_AUDIO_DEV_LIST); // May cause unknown error
// ObservePropertyBool(STR_WIN_MAX, ID_WIN_MAX);
// ObservePropertyBool(STR_WIN_MIN, ID_WIN_MIN);
// ObservePropertyBool(STR_FULL_SCREEN, ID_FULL_SCREEN);
// ObservePropertyString(STR_FS_SCREEN_NAME, ID_FS_SCREEN_NAME);
// ObservePropertyBool(STR_ONTOP, ID_ONTOP);
// ObservePropertyInt64(STR_ONTOP_LEVEL, ID_ONTOP_LEVEL);
// ObservePropertyDouble(STR_WIN_SCALE, ID_WIN_SCALE);
ProcessCmdLine(False);
end;
procedure TMPVBasePlayer.Lock;
begin
m_cLock.Enter;
end;
procedure TMPVBasePlayer.Log(const sMsg: string; bError: Boolean);
begin
// NULL
end;
procedure TMPVBasePlayer.MPVFreeMem(P: Pointer);
begin
FreeMem(P);
end;
procedure TMPVBasePlayer.MPVGetMem(var P: Pointer; nSize: Integer);
begin
GetMem(P, nSize);
end;
procedure TMPVBasePlayer.NotifyFree;
begin
if m_cEventThrd<>nil then m_cEventThrd.Terminate;
m_cLock.Enter;
m_eOnFileOpen := nil;
m_eOnProgress := nil;
m_eOnPropChged := nil;
m_eOnErrMsg := nil;
m_eOnStateChged := nil;
m_cLock.Leave;
end;
function TMPVBasePlayer.ObserveProperty(const sName: string;
nID: UInt64; nFmt: MPVEnum): TMPVErrorCode;
var
sNm: UTF8String;
begin
if m_hMPV=nil then
begin
Result := MPV_ERROR_UNINITIALIZED;
Exit;
end;
sNm := UTF8Encode(sName);
Result := HandleError(mpv_observe_property(m_hMPV, nID, PMPVChar(sNm), nFmt),
'mpv_observe_property');
end;
function TMPVBasePlayer.ObservePropertyBool(const sName: string;
nID: UInt64): TMPVErrorCode;
begin
Result := ObserveProperty(sName, nID, MPV_FORMAT_FLAG);
end;
function TMPVBasePlayer.ObservePropertyDouble(const sName: string;
nID: UInt64): TMPVErrorCode;
begin
Result := ObserveProperty(sName, nID, MPV_FORMAT_DOUBLE);
end;
function TMPVBasePlayer.ObservePropertyInt64(const sName: string;
nID: UInt64): TMPVErrorCode;
begin
Result := ObserveProperty(sName, nID, MPV_FORMAT_INT64);
end;
function TMPVBasePlayer.ObservePropertyString(const sName: string;
nID: UInt64): TMPVErrorCode;
begin
Result := ObserveProperty(sName, nID, MPV_FORMAT_STRING);
end;
procedure TMPVBasePlayer.OnStateChanged;
var
eSC: TMPVStateChanged;
eState: TMPVPlayerState;
begin
m_cLock.Enter;
eSC := m_eOnStateChged;
eState := m_eState;
// update current position
GetPropertyDouble(STR_TIME_POS, m_fCurSec, False);
m_cLock.Leave;
if Assigned(eSC) then
try
eSC(Self, eState);
except
end;
end;
function TMPVBasePlayer.OpenFile(const sFullName: string): TMPVErrorCode;
begin
SetState(mpsLoading);
Result := Command([CMD_LOAD_FILE, sFullName]);
SetPropertyBool(STR_PAUSE, False);
end;
function TMPVBasePlayer.Pause: TMPVErrorCode;
begin
Result := SetPropertyBool(STR_PAUSE, True);
end;
procedure TMPVBasePlayer.ProcessCmdLine(bBeforeInit: Boolean);
begin
// NULL
end;
function TMPVBasePlayer.RemoveSubTitle(const sFile: string): TMPVErrorCode;
begin
Result := CommandStr(CMD_SUB_REMOVE+' '+sFile);
end;
function TMPVBasePlayer.Resume: TMPVErrorCode;
begin
Result := SetPropertyBool(STR_PAUSE, False);
end;
function TMPVBasePlayer.HandleError(nCode: Integer; const sFunc: string;
bRaise: Boolean): TMPVErrorCode;
var
sErr: string;
begin
Result := nCode;
if nCode = MPV_ERROR_SUCCESS then Exit;
sErr := Format('%s=%d', [sFunc, nCode]);
Log(sErr, True);
if bRaise then
begin
raise TMPVException.Create(sErr);
end;
end;
procedure TMPVBasePlayer.SetATrack(const Value: string);
begin
SetAudioTrack(Value);
end;
procedure TMPVBasePlayer.SetAudioDelay(fSec: Double);
begin
SetPropertyDouble(STR_AUDIO_DELAY, fSec);
end;
procedure TMPVBasePlayer.SetAudioDev(const Value: string);
begin
SetPropertyString(STR_AUDIO_DEV, Value);
end;
function TMPVBasePlayer.SetAudioTrack(const sID: string): TMPVErrorCode;
begin
Result := SetTrack(trkAudio, sID, STR_AID);
end;
procedure TMPVBasePlayer.SetCurSec(const Value: Double);
begin
Seek(Value, False);
end;
function TMPVBasePlayer.SetMute(bMute: Boolean): TMPVErrorCode;
begin
Result := SetPropertyBool(STR_MUTE, bMute);
end;
procedure TMPVBasePlayer.SetOnErrMsg(const Value: TMPVErrorMessage);
begin
m_cLock.Enter;
m_eOnErrMsg := Value;
m_cLock.Leave;
end;
procedure TMPVBasePlayer.SetOnFileOpen(const Value: TMPVFileOpen);
begin
m_cLock.Enter;
m_eOnFileOpen := Value;
m_cLock.Leave;
end;
procedure TMPVBasePlayer.SetOnProgChg(const Value: TMPVPropertyChangedEvent);
begin
m_cLock.Enter;
m_eOnPropChged := Value;
m_cLock.Leave;
end;
procedure TMPVBasePlayer.SetOnProgress(const Value: TMPVProgressEvent);
begin
m_cLock.Enter;
m_eOnProgress := Value;
m_cLock.Leave;
end;
procedure TMPVBasePlayer.SetOnStateChg(const Value: TMPVStateChanged);
begin
m_cLock.Enter;
m_eOnStateChged := Value;
m_cLock.Leave;
end;
function TMPVBasePlayer.SetPropertyBool(const sName: string;
Value: Boolean; nID: MPVUInt64): TMPVErrorCode;
var
sNm: UTF8String;
n: MPVInt;
begin
if m_hMPV=nil then
begin
Result := MPV_ERROR_UNINITIALIZED;
Exit;
end;
sNm := UTF8Encode(sName);
if Value then n := 1 else n := 0;
if nID>0 then
Result := HandleError(mpv_set_property_async(m_hMPV, nID, PMPVChar(sNm),
MPV_FORMAT_FLAG, @n), 'mpv_set_property_async(bool)')
else
Result := HandleError(mpv_set_property(m_hMPV, PMPVChar(sNm),
MPV_FORMAT_FLAG, @n), 'mpv_set_property(bool)');
end;
function TMPVBasePlayer.SetPropertyDouble(const sName: string;
Value: Double; nID: MPVUInt64): TMPVErrorCode;
var
sNm: UTF8String;
begin
if m_hMPV=nil then
begin
Result := MPV_ERROR_UNINITIALIZED;
Exit;
end;
sNm := UTF8Encode(sName);
if nID>0 then
Result := HandleError(mpv_set_property_async(m_hMPV, nID, PMPVChar(sNm),
MPV_FORMAT_DOUBLE, @Value), 'mpv_set_property_async(dbl)')
else
Result := HandleError(mpv_set_property(m_hMPV, PMPVChar(sNm),
MPV_FORMAT_DOUBLE, @Value), 'mpv_set_property(dbl)');
end;
function TMPVBasePlayer.SetPropertyInt64(const sName: string;
Value: Int64; nID: MPVUInt64): TMPVErrorCode;
var
sNm: UTF8String;
begin
if m_hMPV=nil then
begin
Result := MPV_ERROR_UNINITIALIZED;
Exit;
end;
sNm := UTF8Encode(sName);
if nID>0 then
Result := HandleError(mpv_set_property_async(m_hMPV, nID, PMPVChar(sNm),
MPV_FORMAT_INT64, @Value), 'mpv_set_property_async(i64)')
else
Result := HandleError(mpv_set_property(m_hMPV, PMPVChar(sNm),
MPV_FORMAT_INT64, @Value), 'mpv_set_property(i64)');
end;
function TMPVBasePlayer.SetPropertyString(const sName,
sValue: string; nID: MPVUInt64): TMPVErrorCode;
var
sNm, sVal: UTF8String;
p: Pointer;
begin
if m_hMPV=nil then
begin
Result := MPV_ERROR_UNINITIALIZED;
Exit;
end;
sNm := UTF8Encode(sName);
sVal := UTF8Encode(sValue);
p := Pointer(sVal); // address of PAnsiChar
if nID>0 then
Result := HandleError(mpv_set_property_async(m_hMPV, nID, PMPVChar(sNm),
MPV_FORMAT_STRING, @p), 'mpv_set_property_async(str)')
else
Result := HandleError(mpv_set_property(m_hMPV, PMPVChar(sNm),
MPV_FORMAT_STRING, @p), 'mpv_set_property(str)');
end;
procedure TMPVBasePlayer.SetSpeed(const Value: Double);
begin
if (Value>0) and (Value<=100) then
begin
SetPropertyDouble(STR_SPEED, Value);
end;
end;
procedure TMPVBasePlayer.SetState(eNewState: TMPVPlayerState);
var
bChg: Boolean;
begin
bChg := False;
m_cLock.Enter;
if m_eState<>eNewState then
begin
m_eState := eNewState;
bChg := True;
end;
m_cLock.Leave;
if bChg then OnStateChanged();
end;
procedure TMPVBasePlayer.SetSTrack(const Value: string);
begin
SetSubTitle(Value);
end;
function TMPVBasePlayer.SetSubTitle(const sIDFile: string; bExternal: Boolean): TMPVErrorCode;
begin
if bExternal then
begin
if FileExists(sIDFile) then
begin
Result := CommandStr(CMD_SUB_ADD+' '+sIDFile);
end else
Result := MPV_ERROR_INVALID_PARAMETER;
end else
Result := SetTrack(trkSub, sIDFile, STR_SID);
end;
procedure TMPVBasePlayer.SetSubTitleDelay(fSec: Double);
begin
SetPropertyDouble(STR_SUB_DELAY, fSec);
end;
function TMPVBasePlayer.SetTrack(eType: TMPVTrackType;
const sID, sPropName: string): TMPVErrorCode;
var
m: Integer;
i: Integer;
n: Int64;
cTrk: TMPVTrackInfo;
begin
n := -1;
m := StrToIntDef(sID, -2);
// find title/id
m_cLock.Enter;
for i := 0 to m_cTrackList.Count-1 do
begin
cTrk := m_cTrackList[i];
if cTrk.TrackType=eType then
begin
if (cTrk.Title=sID) or (cTrk.TrackID=m) then
begin
n := cTrk.TrackID;
Break;
end;
end;
end;
m_cLock.Leave;
if n>=0 then
begin
Result := SetPropertyInt64(sPropName, n);
end else
Result := SetPropertyString(sPropName, sID); // MPV_ERROR_INVALID_PARAMETER
end;
function TMPVBasePlayer.SetVideoTrack(const sID: string): TMPVErrorCode;
begin
Result := SetTrack(trkVideo, sID, STR_VID);
end;
procedure TMPVBasePlayer.SetVol(const Value: Double);
begin
if (Value>0) and (Value<=1000) then
begin
SetPropertyDouble(STR_VOLUME, Value);
end;
end;
procedure TMPVBasePlayer.SetVolMute(const Value: Boolean);
begin
SetMute(Value);
end;
function TMPVBasePlayer.SetVolume(fVol: Double): TMPVErrorCode;
begin
if m_bMute and (fVol>0) then SetMute(False);
Result := SetPropertyDouble(STR_VOLUME, fVol);
end;
procedure TMPVBasePlayer.SetVTrack(const Value: string);
begin
SetVideoTrack(Value);
end;
function TMPVBasePlayer.Stop: TMPVErrorCode;
begin
Result := CommandStr(CMD_STOP);
end;
procedure TMPVBasePlayer.Unlock;
begin
m_cLock.Leave;
end;
function TMPVBasePlayer.UnobserveProperty(nID: UInt64): TMPVErrorCode;
begin
if m_hMPV=nil then
begin
Result := MPV_ERROR_UNINITIALIZED;
Exit;
end;
Result := HandleError(mpv_unobserve_property(m_hMPV, nID),
'mpv_unobserve_property');
end;
{$IFDEF MPV_DYNAMIC_LOAD}
var
g_hMPVLib: HMODULE = 0;
procedure MPVLibFree;
begin
if g_hMPVLib<>0 then
begin
FreeLibrary(g_hMPVLib);
g_hMPVLib := 0;
end;
end;
function ChkGetProc(hMod: HMODULE; const sFN: string): Pointer;
begin
Result := GetProcAddress(hMod, PChar(sFN));
if not Assigned(Result) then
begin
{$IFDEF MSWINDOWS}
MessageBox(GetActiveWindow, 'MPVLibLoaded()', PChar('MPV DLL miss function: '+sFN), MB_ICONERROR);
{$ENDIF}
end
end;
function MPVLibLoaded(const sLibPath: string): Boolean;
var
sLib: string;
begin
Result := Assigned(mpv_client_api_version);
if not Result then
begin
if sLibPath='' then sLib := ExtractFilePath(ParamStr(0))+MPVDLL
else sLib := IncludeTrailingPathDelimiter(sLibPath)+MPVDLL;
MPVLibFree;
g_hMPVLib := SysUtils.SafeLoadLibrary(sLib);
if g_hMPVLib<>0 then
begin
mpv_client_api_version := T_mpv_client_api_version(ChkGetProc(g_hMPVLib, fn_mpv_client_api_version));
mpv_error_string := T_mpv_error_string(ChkGetProc(g_hMPVLib, fn_mpv_error_string));
mpv_free := T_mpv_free(ChkGetProc(g_hMPVLib, fn_mpv_free));
mpv_client_name := T_mpv_client_name(ChkGetProc(g_hMPVLib, fn_mpv_client_name));
mpv_client_id := T_mpv_client_id(ChkGetProc(g_hMPVLib, fn_mpv_client_id));
mpv_create := T_mpv_create(ChkGetProc(g_hMPVLib, fn_mpv_create));
mpv_initialize := T_mpv_initialize(ChkGetProc(g_hMPVLib, fn_mpv_initialize));
mpv_destroy := T_mpv_destroy(ChkGetProc(g_hMPVLib, fn_mpv_destroy));
mpv_terminate_destroy := T_mpv_terminate_destroy(ChkGetProc(g_hMPVLib, fn_mpv_terminate_destroy));
mpv_create_client := T_mpv_create_client(ChkGetProc(g_hMPVLib, fn_mpv_create_client));
mpv_create_weak_client := T_mpv_create_weak_client(ChkGetProc(g_hMPVLib, fn_mpv_create_weak_client));
mpv_load_config_file := T_mpv_load_config_file(ChkGetProc(g_hMPVLib, fn_mpv_load_config_file));
mpv_get_time_us := T_mpv_get_time_us(ChkGetProc(g_hMPVLib, fn_mpv_get_time_us));
mpv_free_node_contents := T_mpv_free_node_contents(ChkGetProc(g_hMPVLib, fn_mpv_free_node_contents));
mpv_set_option := T_mpv_set_option(ChkGetProc(g_hMPVLib, fn_mpv_set_option));
mpv_set_option_string := T_mpv_set_option_string(ChkGetProc(g_hMPVLib, fn_mpv_set_option_string));
mpv_command := T_mpv_command(ChkGetProc(g_hMPVLib, fn_mpv_command));
mpv_command_node := T_mpv_command_node(ChkGetProc(g_hMPVLib, fn_mpv_command_node));
mpv_command_ret := T_mpv_command_ret(ChkGetProc(g_hMPVLib, fn_mpv_command_ret));
mpv_command_string := T_mpv_command_string(ChkGetProc(g_hMPVLib, fn_mpv_command_string));
mpv_command_async := T_mpv_command_async(ChkGetProc(g_hMPVLib, fn_mpv_command_async));
mpv_command_node_async := T_mpv_command_node_async(ChkGetProc(g_hMPVLib, fn_mpv_command_node_async));
mpv_abort_async_command := T_mpv_abort_async_command(ChkGetProc(g_hMPVLib, fn_mpv_abort_async_command));
mpv_set_property := T_mpv_set_property(ChkGetProc(g_hMPVLib, fn_mpv_set_property));
mpv_set_property_string := T_mpv_set_property_string(ChkGetProc(g_hMPVLib, fn_mpv_set_property_string));
mpv_set_property_async := T_mpv_set_property_async(ChkGetProc(g_hMPVLib, fn_mpv_set_property_async));
mpv_get_property := T_mpv_get_property(ChkGetProc(g_hMPVLib, fn_mpv_get_property));
mpv_get_property_string := T_mpv_get_property_string(ChkGetProc(g_hMPVLib, fn_mpv_get_property_string));
mpv_get_property_osd_string := T_mpv_get_property_osd_string(ChkGetProc(g_hMPVLib, fn_mpv_get_property_osd_string));
mpv_get_property_async := T_mpv_get_property_async(ChkGetProc(g_hMPVLib, fn_mpv_get_property_async));
mpv_observe_property := T_mpv_observe_property(ChkGetProc(g_hMPVLib, fn_mpv_observe_property));
mpv_unobserve_property := T_mpv_unobserve_property(ChkGetProc(g_hMPVLib, fn_mpv_unobserve_property));
mpv_event_name := T_mpv_event_name(ChkGetProc(g_hMPVLib, fn_mpv_event_name));
mpv_event_to_node := T_mpv_event_to_node(ChkGetProc(g_hMPVLib, fn_mpv_event_to_node));
mpv_request_event := T_mpv_request_event(ChkGetProc(g_hMPVLib, fn_mpv_request_event));
mpv_request_log_messages := T_mpv_request_log_messages(ChkGetProc(g_hMPVLib, fn_mpv_request_log_messages));
mpv_wait_event := T_mpv_wait_event(ChkGetProc(g_hMPVLib, fn_mpv_wait_event));
mpv_wakeup := T_mpv_wakeup(ChkGetProc(g_hMPVLib, fn_mpv_wakeup));
mpv_set_wakeup_callback := T_mpv_set_wakeup_callback(ChkGetProc(g_hMPVLib, fn_mpv_set_wakeup_callback));
mpv_wait_async_requests := T_mpv_wait_async_requests(ChkGetProc(g_hMPVLib, fn_mpv_wait_async_requests));
mpv_hook_add := T_mpv_hook_add(ChkGetProc(g_hMPVLib, fn_mpv_hook_add));
mpv_hook_continue := T_mpv_hook_continue(ChkGetProc(g_hMPVLib, fn_mpv_hook_continue));
{$IFDEF MPV_ENABLE_DEPRECATED}
mpv_get_wakeup_pipe := T_mpv_get_wakeup_pipe(ChkGetProc(g_hMPVLib, fn_mpv_get_wakeup_pipe));
{$ENDIF MPV_ENABLE_DEPRECATED}
Result := Assigned(mpv_client_api_version);
end;
end;
end;
{$ELSE MPV_DYNAMIC_LOAD}
function MPVLibLoaded(const sLibPath: string): Boolean;
begin
Result := True;
end;
{$ENDIF MPV_DYNAMIC_LOAD}
initialization
finalization
{$IFDEF MPV_DYNAMIC_LOAD}
MPVLibFree;
{$ENDIF MPV_DYNAMIC_LOAD}
end.