ホーム >プログラム >Delphi 6 ローテクTips >TWindowsMediaPlayerでDVD再生

TWindowsMediaPlayerでDVD再生を行おうというページ。



まずはデコーダから
Windows XPにはDVDを再生させるためのデコーダが標準でインストールされていません。
そのままではDVDは再生できないということです。
ということでTWindowsMediaPlayerでDVDを再生させるには事前にPowerDVDやWinDVDなど市販のDVD再生ソフトをインストールする必要があります。
市販のDVD再生ソフトをインストールするのなら、わざわざ自分でDVD再生プログラムを作る必要もないではないか、という感じもしますがそこはそれ。
自分の用途に合わせて作れるというのがポイントなわけです。
再生

TWindowsMediaPlayerでDVDを再生させるのは簡単です。
URLプロパティにDVDのファイルをセットしてcontrols.playメソッドを呼ぶだけでOKです。
あとはDVDのタイトルメニューを操作して視聴します。

procedure TForm1.Button1Click(Sender: TObject);
begin
  if (OpenDialog1.Execute) then begin
    WindowsMediaPlayer1.URL := OpenDialog1.FileName;
    WindowsMediaPlayer1.controls.play;
  end;
end;

URLプロパティではなくcurrentPlaylistにセットしても再生できますが、その場合メディアの再生時間が取得できません。
どのくらいの長さの映画やビデオなのかを取得できないということです。

セットするファイルはDVDディスクのVIDEO_TSフォルダにあるVIDEO_TS.IFOです。
このファイルをセットすると前回の再生の続きから始まります。
それ以外の拡張子が*.IFOか*.VOBのファイルをセットすると前回再生していたタイトルの初めから再生が始まります。
拡張子が*.BUPのファイルはセットしても再生できません。

またプログラム終了時にcontrols.stopを呼ぶと、次の再生時にはレジューム機能はキャンセルされてタイトルメニューの表示から始まります。


sourceURLプロパティ

DVD再生の場合CD再生と同じように、IWMPMediaオブジェクトのsourceURLプロパティの値がファイルのフルパスではなくDVD用のフォーマットに変換されます。

  wmpdvd://ドライブ/タイトル/チャプター?contentdir=フォルダ

というフォーマットで文字列が返ってきます。
この変換はURLプロパティにセットしたらすぐに反映されます。
メディアの情報を取得する場合のようにOpenStateがwmppsMediaOpenになるのを待つ必要はありません。
URLプロパティにファイルをセットしたらcurrentMedia.sourceURLを調べて最初の9文字が「wmpdvd://」であればメディアはDVDであると判断できます。

ドライブはCDの場合と違い、番号ではなくA〜Zのドライブ文字になります。
コロンは含みません。
タイトルはDVDの本編や映像特典などとして格納されているもので、それぞれにチャプターを持ちます。
フォルダを指定できるのでHDDの任意のフォルダ中のDVDビデオも再生できるようです。
このフォルダにドライブ名を含んだフルパスを指定した場合はwmpdvd://の後のドライブは無視されるようです。

ちなみに市販のDVDソフトの場合必ず最初のタイトルメニューを表示する仕様なのか、タイトルやチャプターを指定してもうまくいきませんでした。


タイトル

タイトルはcdromCollectionオブジェクトのItemプロパティのプレイリストオブジェクト中のアイテムとして取り出すことができます。

procedure TForm1.Test1Click(Sender: TObject);
var
  i: Integer;
  l_Playlist: IWMPPlaylist;
  l_Media:    IWMPMedia;
begin
  l_Playlist := WindowsMediaPlayer1.cdromCollection.Item(0).Playlist;
  for i := 0 to l_Playlist.count -1 do begin
    //タイトル
    l_Media := l_Playlist.Item[i];
    myDebug.gpcDebugAdds([Format('%2d', [i]), l_Media.name, l_Media.sourceURL]);
  end;
end;

とはいえ本編以外のタイトルを指定して再生させようとしてもメディアエラーとなって、結局タイトルメニューが表示されるか前回の続きから再生されます。

procedure TForm1.WindowsMediaPlayer1MediaError(Sender: TObject; const pMediaObject: IDispatch);
begin
  //エラーが起きたメディアのsourceURLを表示
  myDebug.gpcDebugAdd('MediaError', IWMPMedia(pMediaObject).sourceURL);
end;

procedure TForm1.Test2Click(Sender: TObject);
var
  l_Media: IWMPMedia;
begin
  //上の図の上から2番目のタイトルを再生させてみる
  l_Media := WindowsMediaPlayer1.cdromCollection.Item(0).Playlist.Item[1];
  myDebug.gpcDebugAdd('Play', l_Media.sourceURL);
  WindowsMediaPlayer1.URL := l_Media.sourceURL;
  WindowsMediaPlayer1.controls.play;
end;

このようなエラーが起きました。
で、結局タイトルメニューが表示されるか前回の再生位置の続きから再生が始まります。


チャプター
チャプターはタイトルの再生が始まった後にcurrentPlaylistに格納されます。
タイトルを再生させてOnOpenStateChangeイベントのNewStateがwmposMediaOpenになった後に取得できるようになります。
タイトルを再生させないうちはチャプター数や各チャプターの情報は取得できません。

PlaylistのItemオブジェクトに格納されているのでcontrols.previousで前のチャプター、controls.nextで次のチャプターへ移動できます。
任意のチャプターを再生させるにはcontrols.currentItemプロパティにセットします。
  //5番目のチャプターに移動
  WindowsMediaPlayer1.controls.currentItem := WindowsMediaPlayer1.currentPlaylist.Item[4];

プレイリストのItemは0ベースのインデックスで指定するので5番目なら引数は4となります。

WindowsMediaPlayer1.controls.playItem(WindowsMediaPlayer1.currentPlaylist.Item[4]);
としたのでは再生はできますがメディアの再生時間の取得ができず0になってしまいます。
WindowsMediaPlayer1.URL := WindowsMediaPlayer1.currentPlaylist.Item[4].sourceURL;
WindowsMediaPlayer1.controls.play;
としても同じで、再生はできますが再生時間が0になります。
var
  i: Integer;
  l_Dvd: IWMPDvd;
begin
 //チャプターサブメニューを削除
  for i := mniDVD_Chapter.Count -1 downto 0 do begin
    mniDVD_Chapter.Items[i].Free;
  end;

  l_Dvd := IWMPPlayer4(WindowsMediaPlayer1.ControlInterface).dvd;
  mniDVD_Chapter.Enabled := l_Dvd.isAvailable['dvd'] and (WindowsMediaPlayer1.currentPlaylist.count > 0);
  if (mniDVD_Chapter.Enabled) then begin
    //チャプターは1から始まる
    for i := 1 to WindowsMediaPlayer1.currentPlaylist.count do begin
      mniDVD_Chapter.Add(NewItem(
        Format('%3d %s', [i, WindowsMediaPlayer1.currentPlaylist.Item[i-1].name]),
        0,
        False,
        True,
        mniDVD_ChapterClick,
        0,
        Format('mniDVD_Chapter_%d', [i])
      ));
      //プレイリストのインデックスをTagにセット
      with mniDVD_Chapter.Items[i-1] do begin
        Tag := (i-1);
        RadioItem := True;
        Checked := WindowsMediaPlayer1.currentMedia.isIdentical[WindowsMediaPlayer1.currentPlaylist.Item[i-1]];
      end;
    end;
  end;
end;
//チャプター再生
procedure TForm1.mniDVD_ChapterClick(Sender: TObject);
begin
  WindowsMediaPlayer1.controls.currentItem := WindowsMediaPlayer1.currentPlaylist.Item[TComponent(Sender).Tag];
end;

mniDVD_Chapter というメニューアイテムのサブメニューとしてチャプターリストを追加する例。


dvdオブジェクト
タイトルメニューやトップメニューへ移動するにはdvdオブジェクトが必要になります。
ただ標準のTWindowsMediaPlayerにはdvdオブジェクトがないので、ControlInterfaceインターフェースをIWMPPlayer4DispかIWMPPlayer4でキャストして使います。
var
  {$WARN SYMBOL_PLATFORM OFF}
  l_Dvd: IWMPPlayer4Disp
  {$WARN SYMBOL_PLATFORM ON}
begin
  {$WARN SYMBOL_PLATFORM OFF}
  l_Dvd := IWMPPlayer4Disp(WindowsMediaPlayer1.ControlInterface).dvd;
  {$WARN SYMBOL_PLATFORM ON}

  if (l_Dvd.isAvailable['titleMenu']) then begin
    l_Dvd.titleMenu
  end;
「シンボル 'IWMPPlayer4Disp' はプラットフォームに依存すると宣言されています」という警告メッセージを回避するため{$WARN SYMBOL_PLATFORM OFF}を使用しています。
再生時間
DVDディスクを変えずに再生した場合前回終了時の位置から再生が始まります。
その場合、TWindowsMediaPlayerのOnOpenStateChangeイベントやOnPlayStateChangeイベントの中でApplication.ProcessMessages;を呼び出すとメディアの再生時間が取得できなくなります。

現在の再生位置が取得できないということではなく、メディアの長さが取得できず0が返ってきてしまうということです。

デバッグ用にMemoやListBoxに情報を出力する場合など注意する必要があるかと思います。
音声メニュー
映画などで日本語吹き替えなどがある場合、一旦DVDのタイトルメニューに戻って音声メニューを表示させ、吹き替えかオリジナルかなどを選択して本編に戻って視聴を続ける、というようなやり方でなくメニューからダイレクトに選択して視聴したいものです。
そのためにはメニューを動的に削除したり追加したりする必要があります。

DVD内のメニューやタイトルによって音声が複数あったりなかったりするので、そのつど確認する必要があります。
具体的にはメニューを開くたびにチェックするのが確実だと思います。

音声メニューの数を取得するには audioLanguageCount を使います。
項目名(「英語 (U.S.)」や「日本語」など)は getLanguageName を使います。
説明(「監督のコメント 1」など)は getAudioLanguageDescription を使います。
これらは IWMPControls3 もしくは IWMPControls3Disp インターフェースを通じてアクセスします。
簡便に済ますには IWMPControls3(WindowsMediaPlayer1.controls) というようにキャストします。
上の図の例だと、
audioLanguageCountは4が返ります。

getLanguageNameはLCIDに対応する言語名を返します。
LCIDというのは大雑把に言えば国別に決められた番号で日本語は1041になります。
この番号を取得するのがgetAudioLanguageIDです。
getAudioLanguageIDの引数には音声メニューのインデックスを与えます。
上の図の例だと1〜4までが有効で、どれも英語なので同じ値(1033)が返ります。
映画などで2番目に日本語の吹き替えのあるものだとgetAudioLanguageID(2)で1041が返ります。
その得られた値がLCIDで、その値をgetLanguageNameに渡して言語名を得るわけです。

getAudioLanguageDescriptionの引数もgetAudioLanguageIDと同じく音声メニューのインデックスを与えます。
すると、もしあれば音声メニューの簡単な説明文が取得できます。
上図ではgetAudioLanguageDescription(4)で「監督のコメント 1」が得られます。
var
  i: Integer;
  l_NewControls: IWMPControls3;
begin
  //音声メニューのサブメニューを削除
  for i := mniDVD_Audio.Count -1 downto 0 do begin
    mniDVD_Audio.Items[i].Free;
  end;

  l_NewControls := IWMPControls3(WindowsMediaPlayer1.controls);
  //音声メニューのサブメニューに音声のメニュー項目を追加
  for i := 1 to l_NewControls.audioLanguageCount do begin
    mniDVD_Audio.Add(NewItem(
      //メニューのキャプション
      Format('%2d %s %s', [
        i,
        l_NewControls.getLanguageName(l_NewControls.getAudioLanguageID(i)), //言語名
        l_NewControls.getAudioLanguageDescription(i)                        //説明
      ]),
      0,
      False,
      True,
      mniDVD_AudioClick,
      0,
      Format('mniDVD_Audio_%d', [i-1])
    ));
    with mniDVD_Audio.Items[i-1] do begin
      Tag := i;
      AutoCheck := True;
      RadioItem := True;
      Checked := (i = F_NewControls.currentAudioLanguageIndex);
    end;
  end;
end;
//音声変更
procedure TForm1.mniDVD_AudioClick(Sender: TObject);
begin
  IWMPControls3(WindowsMediaPlayer1.controls).currentAudioLanguageIndex := TComponent(Sender).Tag;
end;

mniDVD_Audio というメニューアイテムのサブメニューとして音声メニューを追加する例。

字幕
音声メニューと並んで欲しいものに字幕の選択メニューがあります。
が、これをTWindowsMediaPlayerからどうやって操作するのかが解らないのです。
あれやこれや色々やってはみたのですが、エラーが出たり何も取得できなかったり、、と。
まさか操作できないわけはないとも思うのですが…
ソースコード
test.dpr
main.dfm
main.pas
gerenal.pas
myDebug.dfm
myDebug.pas

test.zip - 実行ファイルも含めたパック

DVDの字幕をなんとか操作しようと、あれこれやってるうちにDelphiの「コンポーネント」メニューの「ActiveXコントロールの取り込み」ダイアログ中に「MSWebDVD」なるコンポーネントを見つけました。
ネットで検索してみるとどうやらこれをフォームに貼り付けることで本格的なDVD再生プログラムが作れそうな感じです。
ということで心残りはあるものの、TWindowsMediaPlayerでのDVDの再生は字幕の問題を残して終了、もしくは中断してMSWebDVDでDVDを再生に移行します。


2009-02-24