Unicode対応のTMediaPlayer
Unicodeファイル名に対応したTMediaPlayer。
ちょっと反則気味。
始めに
MediaPlayerよりずっと高機能なTWindowsMediaPlayerを使うことができるので今さらMediaPlayerでもないような気もしますがとりあえず軽いので。
MPlayer.pasのソースコードを直接書き換えます。ということでパーソナル版だと無理なのかな。
まずはMPlayer.pasをプロジェクトのフォルダにコピーします。元のソースをいじると元に戻すのが大変なので、書き換えはコピーしたこのファイルに対して行います。
このコピーしたファイルを削除してしまえば元に戻ります。
コンポーネントのインストールはソースコードを書き換えた後でも行いません。
コンポーネントパレットのSystemのMediaPlayerをフォームに貼り付ければ、後はフォルダにコピーしたMPlayer.pasの変更が反映されます。コンパイルのときにプロジェクトフォルダのソースを優先するようなのでこれでいけます。
宣言部書き換え
TMediaPlayerのタイプ宣言部分。
二箇所書き換えます。
TMediaPlayer = class(TCustomControl)
private
Buttons: array[TMPBtnType] of TMPButton;
FVisibleButtons: TButtonSet;
FEnabledButtons: TButtonSet;
FColoredButtons: TButtonSet;
FAutoButtons: TButtonSet;
Pressed: Boolean;
Down: Boolean;
CurrentButton: TMPBtnType;
CurrentRect: TRect;
ButtonWidth: Integer;
MinBtnSize: TPoint;
FOnClick: EMPNotify;
FOnPostClick: EMPPostNotify;
FOnNotify: TNotifyEvent;
FocusedButton: TMPBtnType;
MCIOpened: Boolean;
FCapabilities: TMPDevCapsSet;
FCanPlay: Boolean;
FCanStep: Boolean;
FCanEject: Boolean;
FCanRecord: Boolean;
FHasVideo: Boolean;
FFlags: Longint;
FWait: Boolean;
FNotify: Boolean;
FUseWait: Boolean;
FUseNotify: Boolean;
FUseFrom: Boolean;
FUseTo: Boolean;
FDeviceID: Word;
FDeviceType: TMPDeviceTypes;
FTo: Longint;
FFrom: Longint;
FFrames: Longint;
FError: Longint;
FNotifyValue: TMPNotifyValues;
FDisplay: TWinControl;
FDWidth: Integer;
FDHeight: Integer;
{*} FElementName: WideString;
FAutoEnable: Boolean;
FAutoOpen: Boolean;
FAutoRewind: Boolean;
FShareable: Boolean;
procedure LoadBitmaps;
procedure DestroyBitmaps;
procedure SetEnabledButtons(Value: TButtonSet);
procedure SetColored(Value: TButtonSet);
procedure SetVisible(Value: TButtonSet);
procedure SetAutoEnable(Value: Boolean);
procedure DrawAutoButtons;
procedure DoMouseDown(XPos, YPos: Integer);
procedure WMLButtonDown(var Message: TWMLButtonDown);
message WM_LButtonDown;
procedure WMLButtonDblClk(var Message: TWMLButtonDblClk);
message WM_LButtonDblClk;
procedure WMMouseMove(var Message: TWMMouseMove);
message WM_MouseMove;
procedure WMLButtonUp(var Message: TWMLButtonUp);
message WM_LButtonUp;
procedure WMSetFocus(var Message: TWMSetFocus);
message WM_SETFOCUS;
procedure WMKillFocus(var Message: TWMKillFocus);
message WM_KILLFOCUS;
procedure WMGetDlgCode(var Message: TWMGetDlgCode);
message WM_GETDLGCODE;
procedure WMSize(var Message: TWMSize);
message WM_SIZE;
function VisibleButtonCount: Integer;
procedure Adjust;
procedure DoClick(Button: TMPBtnType);
procedure DoPostClick(Button: TMPBtnType);
procedure DrawButton(Btn: TMPBtnType; X: Integer);
procedure CheckIfOpen;
procedure SetPosition(Value: Longint);
procedure SetDeviceType( Value: TMPDeviceTypes );
procedure SetWait( Flag: Boolean );
procedure SetNotify( Flag: Boolean );
procedure SetFrom( Value: Longint );
procedure SetTo( Value: Longint );
procedure SetTimeFormat( Value: TMPTimeFormats );
procedure SetDisplay( Value: TWinControl );
procedure SetOrigDisplay;
procedure SetDisplayRect( Value: TRect );
function GetDisplayRect: TRect;
procedure GetDeviceCaps;
function GetStart: Longint;
function GetLength: Longint;
function GetMode: TMPModes;
function GetTracks: Longint;
function GetPosition: Longint;
function GetErrorMessage: string;
function GetTimeFormat: TMPTimeFormats;
function GetTrackLength(TrackNum: Integer): Longint;
function GetTrackPosition(TrackNum: Integer): Longint;
protected
procedure KeyDown(var Key: Word; Shift: TShiftState); override;
procedure Loaded; override;
procedure AutoButtonSet(Btn: TMPBtnType); dynamic;
procedure Notification(AComponent: TComponent; Operation: TOperation); override;
procedure Paint; override;
procedure MMNotify(var Message: TMessage); message MM_MCINOTIFY;
procedure Click(Button: TMPBtnType; var DoDefault: Boolean); reintroduce; dynamic;
procedure PostClick(Button: TMPBtnType); dynamic;
procedure DoNotify; dynamic;
procedure Updated; override;
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
procedure Open;
procedure Close;
procedure Play;
procedure Stop;
procedure Pause; {Pause & Resume/Play}
procedure Step;
procedure Back;
procedure Previous;
procedure Next;
procedure StartRecording;
procedure Eject;
procedure Save;
procedure PauseOnly;
procedure Resume;
procedure Rewind;
property TrackLength[TrackNum: Integer]: Longint read GetTrackLength;
property TrackPosition[TrackNum: Integer]: Longint read GetTrackPosition;
property Capabilities: TMPDevCapsSet read FCapabilities;
property Error: Longint read FError;
property ErrorMessage: string read GetErrorMessage;
property Start: Longint read GetStart;
property Length: Longint read GetLength;
property Tracks: Longint read GetTracks;
property Frames: Longint read FFrames write FFrames;
property Mode: TMPModes read GetMode;
property Position: Longint read GetPosition write SetPosition;
property Wait: Boolean read FWait write SetWait;
property Notify: Boolean read FNotify write SetNotify;
property NotifyValue: TMPNotifyValues read FNotifyValue;
property StartPos: Longint read FFrom write SetFrom;
property EndPos: Longint read FTo write SetTo;
property DeviceID: Word read FDeviceID;
property TimeFormat: TMPTimeFormats read GetTimeFormat write SetTimeFormat;
property DisplayRect: TRect read GetDisplayRect write SetDisplayRect;
published
property ColoredButtons: TButtonSet read FColoredButtons write SetColored
default [btPlay, btPause, btStop, btNext, btPrev, btStep, btBack,
btRecord, btEject];
property Enabled;
property EnabledButtons: TButtonSet read FEnabledButtons write SetEnabledButtons
default [btPlay, btPause, btStop, btNext, btPrev, btStep, btBack,
btRecord, btEject];
property VisibleButtons: TButtonSet read FVisibleButtons write SetVisible
default [btPlay, btPause, btStop, btNext, btPrev, btStep, btBack,
btRecord, btEject];
property Anchors;
property AutoEnable: Boolean read FAutoEnable write SetAutoEnable default True;
property AutoOpen: Boolean read FAutoOpen write FAutoOpen default False;
property AutoRewind: Boolean read FAutoRewind write FAutoRewind default True;
property Constraints;
property DeviceType: TMPDeviceTypes read FDeviceType write SetDeviceType default dtAutoSelect;
property Display: TWinControl read FDisplay write SetDisplay;
{*} property FileName: WideString read FElementName write FElementName;
property Shareable: Boolean read FShareable write FShareable default False;
property Visible;
property ParentShowHint;
property ShowHint;
property PopupMenu;
property TabOrder;
property TabStop default True;
property OnClick: EMPNotify read FOnClick write FOnClick;
property OnContextPopup;
property OnEnter;
property OnExit;
property OnPostClick: EMPPostNotify read FOnPostClick write FOnPostClick;
property OnNotify: TNotifyEvent read FOnNotify write FOnNotify;
end;
{*}印のついてる行でstringをWideStringに書き換えるだけです。
Openメソッド書き換え
次にOpenメソッドを書き換えます。
五箇所です。
procedure TMediaPlayer.Open;
const
{*} DeviceName: array[TMPDeviceTypes] of PWideChar = ('', 'AVIVideo', 'CDAudio', 'DAT',
'DigitalVideo', 'MMMovie', 'Other', 'Overlay', 'Scanner', 'Sequencer',
'VCR', 'Videodisc', 'WaveAudio');
var
{*} OpenParm: TMCI_Open_ParmsW;
DisplayR: TRect;
begin
{ zero out memory }
{*} FillChar(OpenParm, SizeOf(TMCI_Open_ParmsW), 0);
if MCIOpened then Close; {must close MCI Device first before opening another}
OpenParm.dwCallback := 0;
OpenParm.lpstrDeviceType := DeviceName[FDeviceType];
{*} OpenParm.lpstrElementName := PWideChar(FElementName);
FFlags := 0;
if FUseWait then
begin
if FWait then FFlags := mci_Wait;
FUseWait := False;
end
else
FFlags := mci_Wait;
if FUseNotify then
begin
if FNotify then FFlags := FFlags or mci_Notify;
FUseNotify := False;
end;
if FDeviceType <> dtAutoSelect then
FFlags := FFlags or mci_Open_Type;
if FDeviceType <> dtAutoSelect then
FFlags := FFlags or mci_Open_Type
else
FFlags := FFlags or MCI_OPEN_ELEMENT;
if FShareable then
FFlags := FFlags or mci_Open_Shareable;
OpenParm.dwCallback := Handle;
{*} FError := mciSendCommandW(0, mci_Open, FFlags, Longint(@OpenParm));
if FError <> 0 then {problem opening device}
raise EMCIDeviceError.Create(ErrorMessage)
else {device successfully opened}
begin
MCIOpened := True;
FDeviceID := OpenParm.wDeviceID;
FFrames := Length div 10; {default frames to step = 10% of total frames}
GetDeviceCaps; {must first get device capabilities}
if FHasVideo then {used for video output positioning}
begin
Display := FDisplay; {if one was set in design mode}
DisplayR := GetDisplayRect;
FDWidth := DisplayR.Right-DisplayR.Left;
FDHeight := DisplayR.Bottom-DisplayR.Top;
end;
if (FDeviceType = dtCDAudio) or (FDeviceType = dtVideodisc) then
TimeFormat := tfTMSF; {set timeformat to use tracks}
FAutoButtons := [btNext,btPrev]; {assumed all devices can seek to start, end}
if FCanStep then FAutoButtons := FAutoButtons + [btStep,btBack];
if FCanPlay then Include(FAutoButtons, btPlay);
if FCanRecord then Include(FAutoButtons, btRecord);
if FCanEject then Include(FAutoButtons, btEject);
if Mode = mpPlaying then AutoButtonSet(btPlay); {e.g. CD device}
DrawAutoButtons;
end;
end;
TMCI_Open_ParmsをTMCI_Open_ParmsWに、
PCharをPWideCharに、
mciSendCommandをmciSendCommandWにします。
GetDeviceCaps書き換え
上記二点だけで再生できるようになるのですが、Displayプロパティをセットした状態でUnicodeファイル名の音声メディアを読み込むと最初の一度だけエラーが出てしまいます。これを避けるためと、オリジナルでもそうなのですがメディアにビデオがあるかどうかの判定がうまくいかないので修正します。
procedure TMediaPlayer.GetDeviceCaps;
var
DevCapParm: TMCI_GetDevCaps_Parms;
devType: Longint;
RectParms: TMCI_Anim_Rect_Parms;
WorkR: TRect;
begin
FFlags := mci_Wait or mci_GetDevCaps_Item;
DevCapParm.dwItem := mci_GetDevCaps_Can_Play;
mciSendCommand(FDeviceID, mci_GetDevCaps, FFlags, Longint(@DevCapParm) );
FCanPlay := Boolean(DevCapParm.dwReturn);
if FCanPlay then Include(FCapabilities, mpCanPlay);
DevCapParm.dwItem := mci_GetDevCaps_Can_Record;
mciSendCommand(FDeviceID, mci_GetDevCaps, FFlags, Longint(@DevCapParm) );
FCanRecord := Boolean(DevCapParm.dwReturn);
if FCanRecord then Include(FCapabilities, mpCanRecord);
DevCapParm.dwItem := mci_GetDevCaps_Can_Eject;
mciSendCommand(FDeviceID, mci_GetDevCaps, FFlags, Longint(@DevCapParm) );
FCanEject := Boolean(DevCapParm.dwReturn);
if FCanEject then Include(FCapabilities, mpCanEject);
{
* DevCapParm.dwItem := mci_GetDevCaps_Has_Video;
* mciSendCommand(FDeviceID, mci_GetDevCaps, FFlags, Longint(@DevCapParm) );
* FHasVideo := Boolean(DevCapParm.dwReturn);
* if FHasVideo then Include(FCapabilities, mpUsesWindow);
}
DevCapParm.dwItem := mci_GetDevCaps_Device_Type;
mciSendCommand(FDeviceID, mci_GetDevCaps, FFlags, Longint(@DevCapParm) );
devType := DevCapParm.dwReturn;
if (devType = mci_DevType_Animation) or
(devType = mci_DevType_Digital_Video) or
(devType = mci_DevType_Overlay) or
(devType = mci_DevType_VCR) then FCanStep := True;
if FCanStep then Include(FCapabilities, mpCanStep);
FFlags := mci_Anim_Where_Source;
FError := mciSendCommand( FDeviceID, mci_Where, FFlags, Longint(@RectParms) );
WorkR := RectParms.rc;
FDWidth := WorkR.Right - WorkR.Left;
FDHeight := WorkR.Bottom - WorkR.Top;
{*} FHasVideo := ((RectParms.rc.Right > 0) and (RectParms.rc.Bottom > 0));
{*} if (FHasVideo) then begin
{*} Include(FCapabilities, mpUsesWindow);
{*} end else begin
{*} Exclude(FCapabilities, mpUsesWindow);
{*} end;
end; {GetDeviceCaps}
中ほどの四行をコメントアウト、もしくは削除します。
そして終わりの六行を足します。
オリジナルだと開けるメディアは音声だけのものであってもCapabilitiesプロパティにmpUsesWindowがセットされてしまいます。
それをビデオのあるメディアの時だけmpUsesWindowをセットするようにします。
ビデオのある無しはRectを取得してRightとBottomの値が0以上ならビデオありのメディアであると判断しています。
その後ビデオがあればCapabilitiesプロパティにmpUsesWindowをセットし、なければ除外します。
GetMode書き換え
Modeを取得する場合はGetModeを書き換えなければなりません。
function TMediaPlayer.GetMode: TMPModes;
var
StatusParm: TMCI_Status_Parms;
begin
FFlags := mci_Wait or mci_Status_Item;
StatusParm.dwItem := mci_Status_Mode;
{*} FError := mciSendCommandW( FDeviceID, mci_Status, FFlags, Longint(@StatusParm));
Result := TMPModes(StatusParm.dwReturn - 524); {MCI Mode #s are 524+enum}
end;
その他
この他にも色々書き換えなければならない点があるかもしれませんしSaveメソッドも同様に書き換えなければなりません。
基本的には構造体やAPIをUnicode版のものに代えPCharをPWideCharに書き換えるなどすればOKです。
mciCommandをmciCommandWにする、など。
使用例
このようなフォームを作ります。
Panel2のAlignプロパティはalBottom。
Panel1のAlignプロパティはalClientで、DisplayプロパティをPanel1にします。
このPanel1にビデオを表示させます。
procedure TForm1.FormCreate(Sender: TObject);
begin
Panel2.Align := alBottom;
Panel1.Align := alClient;
MediaPlayer1.Display := Panel1;
end;
procedure TForm1.FormResize(Sender: TObject);
begin
with MediaPlayer1
do begin
if (mpUsesWindow
in Capabilities)
then begin
//フォームのサイズにビデオのサイズを合わせる
DisplayRect := Panel1.ClientRect;
end;
end;
end;
procedure TForm1.Button1Click(Sender: TObject);
var
ls_WFile:
WideString;
begin
if (
gfnbOpenFileDialog(ls_WFile))
then begin
with MediaPlayer1
do begin
FileName := ls_WFile;
Open;
if (mpUsesWindow
in Capabilities)
then begin
//ビデオのオリジナルサイズにフォームのサイズを合わせる
Self.SetBounds(
Self.Left,
Self.Top,
(Self.Width - Self.ClientWidth)
+
gfniRectWidth (DisplayRect),
(Self.Height - Self.ClientHeight) +
gfniRectHeight(DisplayRect) + Panel2.Height
);
end;
Play;
end;
end;
end;
end.
ビデオのある無しはCapabilitiesプロパティにmbUsesWindowがあるかどうかで判定します。
ビデオのオリジナルサイズはOpenの直後にDisplayRectの値から出せます。
せっかくなので以前作ったTMediaPlayerのサブセット版もついでにアップしておきます。
2008-06-08: