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_fCurSecnil 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.