之前都是用的delphi下的dspack進行的視頻開發,這個組件其實很好用,就是找解碼器麻煩點,而且還得在客戶的計算機上使用RegSvr32.exe也注冊解碼器,要不有可能播放不了。
結果在查找合適的解碼器過程中,無意搜索到了迅雷的APlayer組件。
迅雷APlayer這個組件提供了一個完整的解碼器合集(核心的流媒體播放技術也是DirectShow和dspack一樣一樣的),下載APlayer的解碼器合集並注冊到系統后,確實在dspack也用的挺好,不過看了APlayer的介紹后發現人家做的更好,雖然是個ActiveX,但是給出的c++示例表示無需顯式注冊即可使用(就是不需要用Regsvr32.exe預先注冊APlayer組件到目標計算機上),而且也無需預先注冊解碼器(也是Regsvr32)到操作系統,只要指定解碼器路徑,APlayer可以自行搜索此路徑查找合適的解碼器,簡直太好了,本來就怕發布到客戶計算機上后由於解碼器問題導致播放不正常(其實開發測試階段已經出現過了),這么個好東西趕快試試。
第一次使用先按照Delphi下的傳統方式來,在開發環境中引入APlayer組件,這個就是個ActiveX控件,添加到組件面板上,建個工程拖到窗體上,響應幾個事件,輕輕松松視頻就開始播放了,呵呵,也不用關心解碼器文件缺不缺了,APlayer組件會查找並指示出來缺少的文件,真是太智能了,省心,好用。
接下來晉級操作,怎么不注冊APlayer.dll就能直接創建ActiveX組件在自己的程序里面呢?看APlayer的示例工程定義了兩個函數(BOOL CreateAPlayerFromFile(void)、HRESULT CreateInstanceFromFile(const TCHAR * pcszPath, REFCLSID rclsid, REFIID riid, IUnknown * pUnkOuter, LPVOID * ppv)),直接通過APlayer.dll就創建了ActiveX組件,不過那個示例工程是C++的,咱們不熟,對照着改了下,沒搞定,於是求助萬能的網絡搜索引擎,目標:Delphi不注冊COM直接使用ActiveX控件並綁定事件,呵呵,感謝前輩們,果然有啊,原文章鏈接:http://blog.csdn.net/love3s/article/details/7411757
照着來吧,按照這位前輩的話,文筆不好直接上代碼吧:
unit Unit1; interface uses Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.OleCtnrs, System.Win.ComObj, EventSink, Winapi.ActiveX, Vcl.ExtCtrls, Vcl.StdCtrls; const CLASS_Player: TGUID = '{A9332148-C691-4B9D-91FC-B9C461DBE9DD}'; type PIUnknown = ^IUnknown; TAtlAxAttachControl = function(Control: IUnknown; hwind: hwnd; ppUnkContainer: PIUnknown): HRESULT; stdcall; _IPlayerEvents = dispinterface ['{31D6469C-1DA7-47C0-91F9-38F0C39F9B89}'] { function OnMessage(nMessage: Integer; wParam: Integer; lParam: Integer): HResult; dispid 1; function OnStateChanged(nOldState: Integer; nNewState: Integer): HResult; dispid 2; function OnOpenSucceeded: HResult; dispid 3; function OnSeekCompleted(nPosition: Integer): HResult; dispid 4; function OnBuffer(nPercent: Integer): HResult; dispid 5; function OnVideoSizeChanged: HResult; dispid 6; function OnDownloadCodec(const strCodecPath: WideString): HResult; dispid 7; function OnEvent(nEventCode: Integer; nEventParam: Integer): HResult; dispid 8; } end; TfrmMain = class(TForm) pnlCom: TPanel; btnOpen: TButton; dlgOpen1: TOpenDialog; btnPath: TButton; procedure FormCreate(Sender: TObject); procedure btnOpenClick(Sender: TObject); procedure btnPathClick(Sender: TObject); private { Private declarations } APlayer: Variant; APlayerCreateSuccess: Boolean; EventSink: TEventSink; function InitAPlayer: Boolean; function CreateComObjectFromDll(CLSID: TGUID; DllHandle: THandle): IUnknown; procedure EventSinkInvoke(Sender: TObject; DispID: Integer; const IID: TGUID; LocaleID: Integer; Flags: Word; Params: tagDISPPARAMS; VarResult, ExcepInfo, ArgErr: Pointer); public { Public declarations } end; var frmMain: TfrmMain; implementation {$R *.dfm} { TForm1 } procedure TfrmMain.btnOpenClick(Sender: TObject); begin if not APlayerCreateSuccess then Exit; if dlgOpen1.Execute(Handle) then begin APlayer.Open(dlgOpen1.FileName); end; end; procedure TfrmMain.btnPathClick(Sender: TObject); begin if not APlayerCreateSuccess then Exit; ShowMessage(APlayer.GetConfig(2)); end; function TfrmMain.CreateComObjectFromDll(CLSID: TGUID; DllHandle: THandle): IUnknown; var Factory: IClassFactory; DllGetClassObject: function(const CLSID, IID: TGUID; var Obj): HResult; stdcall; hr: HRESULT; begin DllGetClassObject := GetProcAddress(DllHandle, 'DllGetClassObject'); if Assigned(DllGetClassObject) then begin hr := DllGetClassObject(CLSID, IClassFactory, Factory); if hr = S_OK then try hr := Factory.CreateInstance(nil, IUnknown, Result); if hr <> S_OK then begin MessageBox(Handle, '創建APlayer實例失敗!', '錯誤', MB_OK + MB_ICONERROR); end; except MessageBox(Handle, PChar('創建APlayer實例失敗!錯誤代碼:' + IntToStr(GetLastError)), '錯誤', MB_OK + MB_ICONERROR); end; end; end; procedure TfrmMain.EventSinkInvoke(Sender: TObject; DispID: Integer; const IID: TGUID; LocaleID: Integer; Flags: Word; Params: tagDISPPARAMS; VarResult, ExcepInfo, ArgErr: Pointer); var ov: OleVariant; begin { 這里需要注明Params這個參數, 包含了事件的參數 如: Params.rgvarg[0] 代表第一個參數 Params.rgvarg[1] 代表第二個參數 ...... Params.rgvarg[65535] 代表第65535個參數 最多65535個參數 具體可以參考 tagDISPPARAMS 的定義 } case dispid of // function OnMessage(nMessage: Integer; wParam: Integer; lParam: Integer): HResult; dispid 1; $00000001: begin end; // function OnStateChanged(nOldState: Integer; nNewState: Integer): HResult; dispid 2; $00000002: begin end; // function OnOpenSucceeded: HResult; dispid 3; $00000003: begin end; // function OnSeekCompleted(nPosition: Integer): HResult; dispid 4; $00000004: begin end; // function OnBuffer(nPercent: Integer): HResult; dispid 5; $00000005: begin end; // function OnVideoSizeChanged: HResult; dispid 6; $00000006: begin end; // function OnDownloadCodec(const strCodecPath: WideString): HResult; dispid 7; $00000007: begin ov := OleVariant(Params.rgvarg[0]); MessageBox(Handle, PChar('缺少解碼器文件:' + VarToStr(ov)), '錯誤', MB_OK + MB_ICONERROR); end; // function OnEvent(nEventCode: Integer; nEventParam: Integer): HResult; dispid 8; $00000008: begin end; end end; procedure TfrmMain.FormCreate(Sender: TObject); begin ReportMemoryLeaksOnShutdown := DebugHook <> 0; APlayerCreateSuccess := InitAPlayer; end; function TfrmMain.InitAPlayer: Boolean; var hModule, hDll: THandle; AtlAxAttachControl: TAtlAxAttachControl; begin hModule := LoadLibrary('atl.dll'); if hModule < 32 then begin Exit(False); end; AtlAxAttachControl := TAtlAxAttachControl(GetProcAddress(hModule, 'AtlAxAttachControl')); EventSink := TEventSink.Create(Self); EventSink.OnInvoke := EventSinkInvoke; if not Assigned(AtlAxAttachControl) then Exit(False); try hDll := LoadLibrary('APlayer.dll'); APlayer := CreateComObjectFromDll(CLASS_Player, hDll) as IDispatch; if VarIsNull(APlayer) then begin Exit(False); end; EventSink.Connect(APlayer, _IPlayerEvents); AtlAxAttachControl(APlayer, pnlCom.Handle, nil); Result := True; except Result := False; end; end; end.
接下來EventSink單元代碼(綁定ActiveX控件事件用的):
unit EventSink; interface uses Winapi.Windows, Winapi.Messages, System.SysUtils, System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Winapi.ActiveX; type TInvokeEvent = procedure(Sender: TObject; DispID: Integer; const IID: TGUID; LocaleID: Integer; Flags: Word; Params: TDispParams; VarResult, ExcepInfo, ArgErr: Pointer) of object; TAbstractEventSink = class(TObject, IUnknown, IDispatch) private FDispatch: IDispatch; FDispIntfIID: TGUID; FConnection: LongInt; FOwner: TComponent; protected { IUnknown } function QueryInterface(const IID: TGUID; out Obj): HRESULT; stdcall; function _AddRef: Integer; stdcall; function _Release: Integer; stdcall; { IDispatch } function GetTypeInfoCount(out Count: Integer): HRESULT; stdcall; function GetTypeInfo(Index, LocaleID: Integer; out TypeInfo) : HRESULT; stdcall; function GetIDsOfNames(const IID: TGUID; Names: Pointer; NameCount, LocaleID: Integer; DispIDs: Pointer): HRESULT; stdcall; function Invoke(DispID: Integer; const IID: TGUID; LocaleID: Integer; Flags: Word; var Params; VarResult, ExcepInfo, ArgErr: Pointer) : HRESULT; stdcall; public constructor Create(AOwner: TComponent); destructor Destroy; override; procedure Connect(AnAppDispatch: IDispatch; const AnAppDispIntfIID: TGUID); procedure Disconnect; end; TEventSink = class(TComponent) private { Private declarations } FSink: TAbstractEventSink; FOnInvoke: TInvokeEvent; protected { Protected declarations } procedure DoInvoke(DispID: Integer; const IID: TGUID; LocaleID: Integer; Flags: Word; var Params; VarResult, ExcepInfo, ArgErr: Pointer); virtual; public { Public declarations } constructor Create(AOwner: TComponent); override; destructor Destroy; override; procedure Connect(AnAppDispatch: IDispatch; const AnAppDispIntfIID: TGUID); published { Published declarations } property OnInvoke: TInvokeEvent read FOnInvoke write FOnInvoke; end; implementation uses ComObj; procedure InterfaceConnect(const Source: IUnknown; const IID: TIID; const Sink: IUnknown; var Connection: LongInt); var CPC: IConnectionPointContainer; CP: IConnectionPoint; i: HRESULT; begin Connection := 0; if Succeeded(Source.QueryInterface(IConnectionPointContainer, CPC)) then if Succeeded(CPC.FindConnectionPoint(IID, CP)) then i := CP.Advise(Sink, Connection); end; procedure InterfaceDisconnect(const Source: IUnknown; const IID: TIID; var Connection: LongInt); var CPC: IConnectionPointContainer; CP: IConnectionPoint; begin if Connection <> 0 then if Succeeded(Source.QueryInterface(IConnectionPointContainer, CPC)) then if Succeeded(CPC.FindConnectionPoint(IID, CP)) then if Succeeded(CP.Unadvise(Connection)) then Connection := 0; end; { TAbstractEventSink } function TAbstractEventSink._AddRef: Integer; stdcall; begin Result := 2; end; function TAbstractEventSink._Release: Integer; stdcall; begin Result := 1; end; constructor TAbstractEventSink.Create(AOwner: TComponent); begin inherited Create; FOwner := AOwner; end; destructor TAbstractEventSink.Destroy; var p: Pointer; begin Disconnect; inherited Destroy; end; function TAbstractEventSink.GetIDsOfNames(const IID: TGUID; Names: Pointer; NameCount, LocaleID: Integer; DispIDs: Pointer): HRESULT; stdcall; begin Result := E_NOTIMPL; end; function TAbstractEventSink.GetTypeInfo(Index, LocaleID: Integer; out TypeInfo) : HRESULT; stdcall; begin Result := E_NOTIMPL; end; function TAbstractEventSink.GetTypeInfoCount(out Count: Integer) : HRESULT; stdcall; begin Count := 0; Result := S_OK; end; function TAbstractEventSink.Invoke(DispID: Integer; const IID: TGUID; LocaleID: Integer; Flags: Word; var Params; VarResult, ExcepInfo, ArgErr: Pointer): HRESULT; stdcall; begin (FOwner as TEventSink).DoInvoke(DispID, IID, LocaleID, Flags, Params, VarResult, ExcepInfo, ArgErr); Result := S_OK; end; function TAbstractEventSink.QueryInterface(const IID: TGUID; out Obj) : HRESULT; stdcall; begin // We need to return the event interface when it's asked for Result := E_NOINTERFACE; if GetInterface(IID, Obj) then Result := S_OK; if IsEqualGUID(IID, FDispIntfIID) and GetInterface(IDispatch, Obj) then Result := S_OK; end; procedure TAbstractEventSink.Connect(AnAppDispatch: IDispatch; const AnAppDispIntfIID: TGUID); begin FDispIntfIID := AnAppDispIntfIID; FDispatch := AnAppDispatch; // Hook the sink up to the automation server InterfaceConnect(FDispatch, FDispIntfIID, Self, FConnection); end; procedure TAbstractEventSink.Disconnect; begin if Assigned(FDispatch) then begin // Unhook the sink from the automation server InterfaceDisconnect(FDispatch, FDispIntfIID, FConnection); FDispatch := nil; FConnection := 0; end; end; { TEventSink } procedure TEventSink.Connect(AnAppDispatch: IDispatch; const AnAppDispIntfIID: TGUID); begin FSink.Connect(AnAppDispatch, AnAppDispIntfIID); end; constructor TEventSink.Create(AOwner: TComponent); begin inherited Create(AOwner); FSink := TAbstractEventSink.Create(Self); end; destructor TEventSink.Destroy; begin FSink.Free; inherited Destroy; end; procedure TEventSink.DoInvoke(DispID: Integer; const IID: TGUID; LocaleID: Integer; Flags: Word; var Params; VarResult, ExcepInfo, ArgErr: Pointer); begin if Assigned(FOnInvoke) then FOnInvoke(Self, DispID, IID, LocaleID, Flags, TDispParams(Params), VarResult, ExcepInfo, ArgErr); end; end.
循着前輩的腳步果然很容易並順利的解決了問題,我在APlayer論壇看有人問怎么在Delphi下也可以免注冊使用APlayer組件呢,呵呵,現在有答案了!而且我們掌握了一個重要的Delphi技能“Delphi不注冊COM直接使用ActiveX控件並綁定事件”,開心!特此記錄。
后附程序執行的截圖:
1、程序設計界面,只是放置了兩個按鈕、一個OpenDialog、一個Panel(作為APlayer組件的容器)。
2、程序運行后,可以看到APlayer組件成功創建到了Panel上,讀取APlayer的解碼器路徑,和APlayer.dll在同一目錄下,如果用的注冊ActiveX的方式並拖拽到窗體上進行開發的,自己試試就會發現解碼器路徑固定在“C:\Users\Public\Thunder Network\APlayer”且無法修改。如果解碼器路徑固定了會導致在客戶端計算機部署時更復雜些,不如在本地目錄方便,況且還得在客戶計算機上注冊APlayer組件,忒麻煩了。呵呵,免注冊真好!
3、播放