ホーム >プログラム >Delphi 6 ローテクTips >MSWebDVDでDVD再生その2

TMSWebDVDでDVDプレーヤーソフトを作ろうというページ。



DSPack
MSWebDVDでDVDプレーヤーを作るためにはDirectShowのヘッダーをDelphi用に翻訳したユニットをインストールしておくと便利です。
なくてもなんとかなるのですが自分で宣言しないといけなかったりして手間がかかります。
DSPackの圧縮ファイルを解凍するとDirectX用のヘッダーユニットの他にもサンプルソースコードなどが色々あります。
中にはDVDプレーヤーのサンプル(MSWebDVDのではないですが)もあり、色々面白そうなのでインストールして損はないと思います。
それらのサンプルを動かすにはDirectX9_Dx.dpkとDSPack_Dx.dpkとDSPackDesign_Dx.dpkの三つをインストールする必要があります。
インストールのやり方はreadme.htmlに(英文ですが)書いてあります。D6の場合なら、
  1. ライブラリの検索パスにsrcフォルダ中のDirectx9とDSPackの二つのフォルダを追加。
  2. packagesフォルダ中のDirectX9_D6.dpkを読み込んでコンパイル。
    インストールはしません。
  3. packagesフォルダ中のDSPack_D6.dpkを読み込んでコンパイル。
    これもインストールはしません。
  4. 最後にpackagesフォルダ中のDSPackDesign_D6.dpkを読み込んでコンパイルしてインストール。
これでOKです。
DirectX9_D6.dpkとDSPack_D6.dpkはコンパイルするだけでインストールはしません。
DSPackDesign_D6.dpkをインストールするだけでOKです。

DSPackをインストールしない場合なら、解凍したファイル中のDirectShow9.pasのあるフォルダ(src\Directx9)をDelphiのライブラリのパスに追加します。
再生/停止
準備が整ったところで再生、停止、一時停止、チャプター移動などの操作の実装に移ります。
難しいことはありません。
PlayメソッドやStopメソッドなど対応するものを呼ぶだけです。
ただDVDの操作ではエラーが頻繁に起きるので tryexceptで囲むのが良いと思います。
uses
  DirectShow9;


const
{UOPValid メソッド
http://msdn.microsoft.com/ja-jp/library/cc371042.aspx
}

  dvdUOPValid_PlayTitle                    = 0;
  dvdUOPValid_PlayAtTime                   = 0;
  dvdUOPValid_PlayAtTimeInTitle            = 0;
  dvdUOPValid_PlayChapter                  = 1;
  // dvdUOPValid_PlayTitle                   = 2;
  dvdUOPValid_Stop                         = 3; //(MSVidCtlオブジェクトには適用されない)
  dvdUOPValid_ReturnFromSubmenu            = 4;
  // dvdUOPValid_PlayChapter                 = 5;
  dvdUOPValid_PlayPrevChapter              = 6;
  dvdUOPValid_ReplayChapter                = 6;
  dvdUOPValid_PlayNextChapter              = 7;
  dvdUOPValid_PlayForwards                 = 8;  //順方向の再生が許される。
  dvdUOPValid_PlayBackwards                = 9;  //逆方向の再生が許される。
  dvdUOPValid_DVD_MENU_Title_ShowMenu      = 10; //パラメータ値2(DVD_MENU_Title) を指定したShowMenu
  dvdUOPValid_DVD_MENU_Root_ShowMenu       = 11; //パラメータ値3(DVD_MENU_Root) を指定したShowMenu
  dvdUOPValid_DVD_MENU_Subpicture_ShowMenu = 12; //パラメータ値4(DVD_MENU_Subpicture) を指定したShowMenu
  dvdUOPValid_DVD_MENU_Audio_ShowMenu      = 13; //パラメータ値5(DVD_MENU_Audio) を指定したShowMenu
  dvdUOPValid_DVD_MENU_Angle_ShowMenu      = 14; //パラメータ値6(DVD_MENU_Angle) を指定したShowMenu
  dvdUOPValid_DVD_MENU_Chapter_ShowMenu    = 15; //パラメータ値7(DVD_MENU_Chapter) を指定したShowMenu
  dvdUOPValid_Resume                       = 16;
  dvdUOPValid_SelectLeftButton             = 17;
  dvdUOPValid_SelectRightButton            = 17;
  dvdUOPValid_SelectUpperButton            = 17;
  dvdUOPValid_SelectLowerButton            = 17;
  dvdUOPValid_StillOff                     = 18;
  dvdUOPValid_Pause                        = 19; //(MSVidCtl オブジェクトには適用されない)
  dvdUOPValid_CurrentAudioStream           = 20;
  dvdUOPValid_CurrentSubpictureStream      = 21;
  dvdUOPValid_CurrentAngle                 = 22;
  dvdUOPValid_SelectParentalLevel          = 22;
  dvdUOPValid_KaraokeAudioPresentationMode = 23;
  dvdUOPValid_PriorityVideoMode            = 24; //優先ビデオ モードを選択する。
//Play
procedure TForm1.mniPlay_PlayClick(Sender: TObject);
begin
 //エラーは無視
  try
    MSWebDVD1.Play;
  except
  end;
end;

//Stop
procedure TForm1.mniPlay_StopClick(Sender: TObject);
begin
  //UOPValidメソッドでStop可能かチェックしようとしてもDVDディスクが入っていなければエラーが起きる
  try
    if (MSWebDVD1.UOPValid(dvdUOPValid_Stop)) then begin
      MSWebDVD1.Stop;
    end;
  except
  end;
end;
一応UOPValidメソッドでStopが使用可能かどうか判定していますが、tryexceptで囲っているのでなくてもよいと思います。
使う場合は上記のように定数を宣言しておいて使ったほうが後々分かりやすくて良いでしょう。
//Pause
procedure TForm1.mniPlay_PauseClick(Sender: TObject);
begin
  try
    if (MSWebDVD1.UOPValid(dvdUOPValid_Pause)) then begin
      MSWebDVD1.Pause;
    end;
  except
  end;
end;

//取り出し
procedure TForm1.mniFile_EjectClick(Sender: TObject);
begin
  mniPlay_StopClick(nil);
  try
    MSWebDVD1.Eject;
  except
  end;
end;
取り出しは一応Stopさせてから行っています。
チャプターの移動は範囲外の数値を与えないように数値の制限を行います。
function gfniNumLimit(iNum: Integer; iMin, iMax: Integer): Integer;
//数値の範囲制限
var
  li_Tmp: Integer;
begin
  if (iMin = iMax) then begin
    Result := iMin;
  end else begin
    if (iNum < iMin) then begin
      Result := iMin;
    end else if (iNum > iMax) then begin
      Result := iMax;
    end else begin
      Result := iNum;
    end;
  end;
end;

function gfniDVDChaptersCountGet(DVD: TMSWebDVD): Integer;
{現在のタイトルのチャプター数を返す。
http://msdn.microsoft.com/ja-jp/library/cc354615.aspx
}

begin
  Result := DVD.GetNumberOfChapters(DVD.CurrentTitle);
end;
//前のチャプター
procedure TForm1.mniPlay_PrevClick(Sender: TObject);
var
  li_Chapter: Integer;
begin
  try
    if (MSWebDVD1.UOPValid(dvdUOPValid_PlayPrevChapter)) then begin
      //チャプターは1〜999
      li_Chapter := gfniNumLimit(MSWebDVD1.CurrentChapter - 1, 1, gfniDVDChaptersCountGet(MSWebDVD1));
      MSWebDVD1.PlayChapter(li_Chapter);
    end;
  except
  end;
end;

//次のチャプター
procedure TForm1.mniPlay_NextClick(Sender: TObject);
var
  li_Chapter: Integer;
begin
  try
    if (MSWebDVD1.UOPValid(dvdUOPValid_PlayPrevChapter)) then begin
      //チャプターは1〜999
      li_Chapter := gfniNumLimit(MSWebDVD1.CurrentChapter + 1, 1, gfniDVDChaptersCountGet(MSWebDVD1));
      MSWebDVD1.PlayChapter(li_Chapter);
    end;
  except
  end;
end;

時間表示
再生時間の表示はTMediaPlayerやTWindowsMediaPlayerではタイマーを使う必要がありました。
MSWebDVDではタイマーを使っても良いのですがDVDNotifyイベントが0.4〜1秒ごとにイベントが起きるのでそれを利用することもできます。
procedure TForm1.MSWebDVD1DVDNotify(Sender: TObject; lEventCode: Integer; lParam1, lParam2: POleVariant);
begin
  case lEventCode of
    EC_DVD_CURRENT_HMSF_TIME: begin
      //http://msdn.microsoft.com/ja-jp/library/cc354356.aspx
      if (DVD_DOMAIN(MSWebDVD1.CurrentDomain) = DVD_DOMAIN_Title) then begin
        //フレーム数は必要ないので秒数までを取得
        lblTime.Caption := ' ' + Copy(MSWebDVD1.CurrentTime, 1, 8);
      end;
    end;
  end;
end;
lEventCodeを判定してEC_DVD_CURRENT_HMSF_TIMEのときに時間表示の処理に入ります。
ドメインがDVD_DOMAIN_Titleのときでないとエラーが起きるのでCurrentDomainの値を調べます。
CurrentDomainプロパティはIntegerであるのに対しDVD_DOMAIN_Titleは列挙型であるので直接比較はできません。
CurrentDomainをDVD_DOMAINでキャストして比較します。
この判定を行わないと0.4秒〜1秒間隔でエラーダイアログが出てしまい手がつけられなくなります。

上の例では再生時間の取得にCurrentTimeプロパティを使用していますが、lParam1の値を使ってもOKです。
procedure TForm1.MSWebDVD1DVDNotify(Sender: TObject; lEventCode: Integer; lParam1, lParam2: POleVariant);
begin
  case lEventCode of
    EC_DVD_CURRENT_HMSF_TIME: begin
      //http://msdn.microsoft.com/ja-jp/library/cc354356.aspx
      if (ULONG(lParam1) > 0) then begin
        if (DVD_HMSF_TIMECODE(ULONG(lParam1)).bHours > 0) then begin
          lblTime.Caption := Format(' %d:%.2d:%.2d', [
                                DVD_HMSF_TIMECODE(ULONG(lParam1)).bHours,
                                DVD_HMSF_TIMECODE(ULONG(lParam1)).bMinutes,
                                DVD_HMSF_TIMECODE(ULONG(lParam1)).bSeconds
                             ]);
        end else begin
          lblTime.Caption := Format(' %.2d:%.2d', [
                                DVD_HMSF_TIMECODE(ULONG(lParam1)).bMinutes,
                                DVD_HMSF_TIMECODE(ULONG(lParam1)).bSeconds
                             ]);
        end;
      end;
    end;
  end;
end;
こちらの方が細かい時間表示の仕方はしやすでしょう。
シークバー
シークバーをスクロールバーで実装する場合は以下のようになります。
function gfniDVDTimeCodeToSec(sTimeCode: WideString): Integer;
//'hh:mm:ss:ff'フォーマットの文字列を秒数に変換して返す。
begin
  Result :=
    StrToInt(Copy(sTimeCode, 1, 2)) * 60 * 60 +
    StrToInt(Copy(sTimeCode, 4, 2)) * 60 +
    StrToInt(Copy(sTimeCode, 7, 2));
end;
TMSWebDVDでは再生位置を秒数で取得するプロパティやメソッドはないので 'hh:mm:ss:ff' というフォーマットの文字列を秒数に変換したり逆に秒数からこの形式の文字列に変換して使います。
procedure TForm1.MSWebDVD1DVDNotify(Sender: TObject; lEventCode: Integer; lParam1, lParam2: POleVariant);
begin
  case lEventCode of
    EC_DVD_DOMAIN_CHANGE: begin
      //ドメイン
      //http://msdn.microsoft.com/ja-jp/library/cc354371.aspx
      lblDomain.Caption := gfnsDVDDomainStrGet(DWORD(lParam1));
      //↓はlParam1で判定してはいけない
      if (DVD_DOMAIN(MSWebDVD1.CurrentDomain) = DVD_DOMAIN_Title) then begin
        lblTotalTime.Caption := '/' + Copy(MSWebDVD1.TotalTitleTime, 1, 8);
        ScrollBar1.Max := gfniDVDTimeCodeToSec(MSWebDVD1.TotalTitleTime);
      end;

    EC_DVD_CURRENT_HMSF_TIME: begin
      //時間取得
      //http://msdn.microsoft.com/ja-jp/library/cc354356.aspx
      if (DVD_DOMAIN(MSWebDVD1.CurrentDomain) = DVD_DOMAIN_Title) then begin
        //フレーム数は必要ないので秒数までを取得
        lblTime.Caption := ' ' + Copy(MSWebDVD1.CurrentTime, 1, 8);
      end;
    end;
  end;
end;
まずDVDNotifyイベントでドメインの変更をとらえます(lEventCodeを判定してEC_DVD_DOMAIN_CHANGEのとき)
そしてCurrentDomainプロパティが「現在のタイトルメニュー表示(DVD_DOMAIN_Title)」であれば処理に入ります。
このときlParam1の値を利用しようとしてもうまくいきません。
CurrentDomainプロパティの値を使うようにします。
ドメインが「現在のタイトルメニュー表示」になればDVDビデオの長さを取得できるようになるのでその値をスクロールバーのMaxプロパティにセットします。
function gfnsSecToDVDTimeCode(iSec: Integer): WideString;
//秒数から'hh:mm:ss:ff'に変換して返す。
begin
  Result := WideFormat('%.2d:%.2d:%.2d:00', [
              iSec div (60 * 60),  //時
              iSec div 60 mod 60, //分
              iSec mod 60          //秒
            ]);
end;
//シークバー
procedure TForm1.ScrollBar1Scroll(Sender: TObject; ScrollCode: TScrollCode; var ScrollPos: Integer);
begin
  if (ScrollCode = scEndScroll) then begin
    if (MSWebDVD1.CurrentDomain = DVD_DOMAIN_Title) then begin
      MSWebDVD1.PlayAtTime(gfnsSecToDVDTimeCode(ScrollPos));
    end;
  end;
end;
シークバーでの再生位置の移動はTMediaPlayerやTWindowsMediaPlayerなどのようにPositionのセットを行うパターンではなく再生位置を指定したPlayコマンドを使うという感じになります。
それがPlayAtTimeメソッドで、引数には秒数ではなく'hh:mm:ss:ff'(時:分:秒:フレーム)というフォーマットの文字列を与えます。
ボリューム、バランス
シークバーのついでにボリュームとバランスを実装してみます。
バランスはTWindowsMediaPlayerと同じような感じですがボリュームに関しては違います。
ちょっとややこしいです。 ということでネットで調べて上記のサイトを参考にしてみました。
//ボリューム
procedure TForm1.ScrollBar2Scroll(Sender: TObject; ScrollCode: TScrollCode; var ScrollPos: Integer);
begin
  //http://d.hatena.ne.jp/DouglasDourg/20060911/1157936571
  if (ScrollPos = 0) then begin
    MSWebDVD1.Volume := -10000;
  end else begin
    MSWebDVD1.Volume := Trunc(LogN(3, ScrollPos / ScrollBar2.Max) * 1000);
  end;
end;
MSDNのVolumeプロパティの説明では
このプロパティは、デフォルト値が 0 の読み書き可能なプロパティである。
最大ボリュームは 0、無音は 10,000 。
スケールは対数である。
必要なデシベル値を 100 倍する (たとえば、10,000 = 100 dB)。
となっているのでMaxを10,000、Min0にしそうですがそうではありません。
Volumeの値として有効なのは-10,000〜0です。
ではMaxを0、Minを-10,000にすればよいのかというとそれもちょっと違います。
Maxを0、Minを-100にしてスクロールバーの位置を二乗した値をVolumeにセットすれば良いのかとも思いましたが、これだと減衰量が大きすぎるようでボリューム半分でもえらく小音量になってしまいました。

参考サイトの式をDelphiに当てはめると下記のようになるのだと思われます。
スクロールバーのMinは0、Maxは100とします。
  //http://d.hatena.ne.jp/DouglasDourg/20060911/1157936571
  if (ScrollPos = 0) then begin
   MSWebDVD1.Volume := -10000;
  end else begin
    MSWebDVD1.Volume := Trunc(Log10(ScrollPos / ScrollBar2.Max) * 1000);
  end;
これで実際にボリューム調整してみるとWindowsMediaPlayerに比べて少し減衰量が足りないような感じです。
色々試してみてLog10関数ではなくLogN関数で底を3あたりにしてみたらちょうど良い感じでした。
ちなみにLog10関数やLogN関数はMathユニットにあるのでPro以上でないと使えないのかな。
//バランス
procedure TForm1.ScrollBar3Scroll(Sender: TObject; ScrollCode: TScrollCode; var ScrollPos: Integer);
begin
  //スクロールバーで真ん中にピタリと止めるのは難しいので一定の幅で強制的に真ん中にする
  if (ScrollCode = scEndScroll) and (-1000 < ScrollPos) and (ScrollPos < 1000) then begin
    ScrollPos := 0;
  end;
  MSWebDVD1.Balance := ScrollPos;
end;
バランスはMaxを10,000、Minを-10,000にします。こちらはボリュームの場合と違いスクロールバーの位置をそのままBalanceの値にします。

スクロールバーでピタリと真ん中の0の位置にセットするのは結構むずかしいのでちょっと小細工しています。
スクロールバーを中央付近で離した場合強制的に0に戻すようにしています。
DVDメニュー
//メニューイベント
procedure TForm1.MSWebDVD1ShowMenu(Sender: TObject; __MIDL_0011: TOleEnum; bEnabled: WordBool);
begin
  case __MIDL_0011 of
    dvdMenu_Title:      mniMenu_Title.Enabled      := bEnabled;
    dvdMenu_Root:       mniMenu_Root.Enabled       := bEnabled;
    dvdMenu_Subpicture: mniMenu_Subpicture.Enabled := bEnabled;
    dvdMenu_Audio:      mniMenu_Audio.Enabled      := bEnabled;
    dvdMenu_Angle:      mniMenu_Angle.Enabled      := bEnabled;
    dvdMenu_Chapter:    mniMenu_Chapter.Enabled    := bEnabled;
  end;
end;
//タイトルメニュー
procedure TForm1.mniMenu_TitleClick(Sender: TObject);
begin
  MSWebDVD1.ShowMenu(dvdMenu_Title);
end;

//ルートメニュー
procedure TForm1.mniMenu_RootClick(Sender: TObject);
begin
  MSWebDVD1.ShowMenu(dvdMenu_Root);
end;

//字幕メニュー
procedure TForm1.mniMenu_SubpictureClick(Sender: TObject);
begin
  MSWebDVD1.ShowMenu(dvdMenu_Subpicture);
end;

//音声メニュー
procedure TForm1.mniMenu_AudioClick(Sender: TObject);
begin
  MSWebDVD1.ShowMenu(dvdMenu_Audio);
end;

//アングルメニュー
procedure TForm1.mniMenu_AngleClick(Sender: TObject);
begin
  MSWebDVD1.ShowMenu(dvdMenu_Angle);
end;

//チャプターメニュー
procedure TForm1.mniMenu_ChapterClick(Sender: TObject);
begin
  MSWebDVD1.ShowMenu(dvdMenu_Chapter);
end;
このメニューは右クリックして出てくるポップアップメニューのようなメニューではありません。
DVDビデオにある音声や字幕を選択するメニューページやチャプター選択のメニューページへ移動するものです。

その1へ