ホーム >プログラム >Delphi 6 ローテクTips >TWindowsMediaPlayerのタイミング

TWindowsMediaPlayerでメディアの長さや幅、高さなどを取得するタイミングの考察。

※このページはDelphi標準のTMediaPlayerではなく、ActiveXの取り込みで取り込んだTWindowsMediaPlayerのタイミングを考察しているページです。


TMediaPlayerコンポーネントはFileNameプロパティにファイル名をセットしてOpenすればメディアの長さや大きさを取得できます。
ところがTWindowsMediaPlayerではそうはいきません。
どうやらTMediaPlayerとは違って、URLプロパティにファイルをセットしてplayメソッドを呼んだ後にイベントの中でパラメータを見て処理を行うというやり方をするようです。
そこで関連すると思われるOpenStateChangeイベントとPlayStateChangeイベントの発生状況と順序を調べてみようというのがこのページの趣旨です。

procedure Form1.Button1Click(Sender: TObject);
begin
  if (OpenDialog1.Execute) then begin
    myDebug.gpcDebugAdd('(1)');           //--- (1) スタート
    WindowsMediaPlayer1.URL := OpenDialog1.FileName;
    myDebug.gpcDebugAdd('(2)');           //--- (2) URLプロパティのセット後
    WindowsMediaPlayer1.controls.play;
    myDebug.gpcDebugAdd('(3)');           //--- (3) playメソッドの後
  end;
end;

procedure Form1.WindowsMediaPlayer1OpenStateChange(Sender: TObject; NewState: Integer);
begin
  myDebug.gpcDebugAdd('Open', NewState);
end;

procedure Form1.WindowsMediaPlayer1PlayStateChange(Sender: TObject; NewState: Integer);
begin
  myDebug.gpcDebugAdd('Play', NewState);
end;

実行結果です。
なにやら色々とイベントが発生しているようです。
数字だけでは分かりづらいのでマイクロソフトのリファレンス(openStateplayState)と照らし合わせて表にしてみました。


状態 説明
(1) WindowsMediaPlayer1.URL := FileName;
Open 1 PlaylistChanging 新しい再生リストが読み込まれようとしています。
Play 9 Transitioning 新しいメディアを準備中です。
Open 7 PlaylistChanged 新しい再生リストが currentPlaylist に割り当てられました。
(ここでcurrnetMediaに値が入る)
(これ以前にcurrentMediaにアクセスするとエラーになる)
Open 6 PlaylistOpenNoMedia 再生リストは開いています。
Play 10 Ready 再生を開始する準備ができています。
(2) URLへの代入から戻る。
WindowsMediaPlayer1.controls.play;
Open 21 OpeningUnknownURL 不明な種類の URL を開いています。
Play 9 Transitioning 新しいメディアを準備中です。
Open 12 MediaOpening メディアは取得済みで、現在開いているところです。
(3) controls.playから戻る。
Open 13 MediaOpen メディアは現在開いています。
(ここで長さや幅などがセットされ、ようやく値を取得できる)
Play 3 Playing 現在のメディア クリップは再生中です。
(再生が始まる)
--- 再生中 ---
Play 8 MediaEnded メディアの再生が完了し、最後の位置にあります。
Play 9 Transitioning 新しいメディアを準備中です。
Play 1 Stopped 現在のメディア クリップの再生が停止されています。

かえって分かりづらい説明もありますが、ここで重要なのはOpenStateChangeの13, MediaOpenです。このパラメータが来た後であれば長さや大きさの取得が可能になります。
残念なことにこのイベントが起きるのはplayメソッドを呼んで戻ってきた更にその後です。したがって

    WindowsMediaPlayer1.URL := OpenDialog1.FileName;
    WindowsMediaPlayer1.controls.play;
    myDebug.gpcDebugAdd(WindowsMediaPlayer1.currentMedia.durationString);

とやっても'00:00'が表示されるだけです。
URLにファイルを代入して戻った時点で分かるようになっていれば素直で分かりやすいのですがそうはなっていません。
結局上の例でいえばButton1Clickとは別のところで長さや大きさを取得するしかないのでちょっと見通しが悪くなってしまいますがそういうものなのだと納得するのが肝要です。

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

procedure TForm1.WindowsMediaPlayer1OpenStateChange(Sender: TObject; NewState: Integer);
begin
  if (NewState = wmposMediaOpeng) then begin
    //これ以降、長さや大きさの取得可能
  end;
end;

ちなみにOpenStateとPlayState定数名ですがリファレンスの「状態」名称の頭にOpenStateならwmposをPlayStateならwmppsをつけます。


次にリストにあるファイルを連続再生する場合を考えてみます。

同じ曲を連続再生する時はPlayStateChangeイベントでwmppsStoppedをつかまえてplayメソッドを加えるだけでいけます。

procedure Form1.WindowsMediaPlayer1PlayStateChange(Sender: TObject; NewState: Integer);
begin
  myDebug.gpcDebugAdd('Play', NewState);
  if (NewState = wmppsStopped) then begin
    plyMedia.controls.play;
  end;
end;
Play 3 Playing 現在のメディア クリップは再生中です。
Play 8 MediaEnded メディアの再生が完了し、最後の位置にあります。
Play 9 Transitioning 新しいメディアを準備中です。
Play 1 Stopped 現在のメディア クリップの再生が停止されています。
Play 3 Playing 現在のメディア クリップは再生中です。
Play 8 MediaEnded メディアの再生が完了し、最後の位置にあります。
Play 9 Transitioning 新しいメディアを準備中です。
Play 1 Stopped 現在のメディア クリップの再生が停止されています。
--- 以下同じことの繰り返し ---

ところが違う曲を連続再生させようとするとこれではうまくいきません。なぜか二曲目を読み込んだ後、再生することなく次の曲を読み込みんだ挙句途中で再生せずに処理が止まってしまいます。

procedure Form1.Button1Click(Sender: TObject);
begin
  myDebug.gpcDebugAdd('(1)');           //--- (1)
  WindowsMediaPlayer1.URL := ListBox1.Items.[0];
  myDebug.gpcDebugAdd('(2)');           //--- (2)
  WindowsMediaPlayer1.controls.play;
  myDebug.gpcDebugAdd('(3)');           //--- (3)
end;

procedure Form1.WindowsMediaPlayer1PlayStateChange(Sender: TObject; NewState: Integer);
begin
  myDebug.gpcDebugAdd('Play', NewState);
  if (NewState = wmppsStopped) then begin
    myDebug.gpcDebugAdd('(1)');           //--- (1)
    ListBox1.ItemsIndex := ListBox1.ItemIndex + 1;
    WindowsMediaPlayer1.URL := ListBox1.Items.[ListBox1.ItemIndex];
    myDebug.gpcDebugAdd('(2)');           //--- (2)
    WindowsMediaPlayer1.controls.play;
    myDebug.gpcDebugAdd('(3)');           //--- (3)
  end;
end;

状態 説明
(1) WindowsMediaPlayer1.URL := ListBox1.Items[0];
Open 1 PlaylistChanging 新しい再生リストが読み込まれようとしています。
Play 9 Transitioning 新しいメディアを準備中です。
Open 8 MediaChanging 新しいメディアが読み込まれようとしています。
Open 6 PlaylistOpenNoMedia 再生リストは開いています。
Play 10 Ready 再生を開始する準備ができています。
(2) WindowsMediaPlayer1.controls.play;
Open 21 OpeningUnknownURL 不明な種類の URL を開いています。
Play 9 Transitioning 新しいメディアを準備中です。
Open 12 MediaOpening メディアは取得済みで、現在開いているところです。
(3) controls.playから戻る。
Open 13 MediaOpen メディアは現在開いています。
Play 3 Playing 現在のメディア クリップは再生中です。
--- 再生中 ---
Play 8 MediaEnded メディアの再生が完了し、最後の位置にあります。
Play 9 Transitioning 新しいメディアを準備中です。
Play 1 Stopped 現在のメディア クリップの再生が停止されています。
(1) 二曲目読み込み
WindowsMediaPlayer1.URL := ListBox1.Items[1];
Open 1 PlaylistChanging 新しい再生リストが読み込まれようとしています。
Play 9 Transitioning 新しいメディアを準備中です。
Open 8 MediaChanging 新しいメディアが読み込まれようとしています。
Open 6 PlaylistOpenNoMedia 再生リストは開いています。
Play 10 Ready 再生を開始する準備ができています。
Open 7 PlaylistChanged 新しい再生リストが currentPlaylist に割り当てられました。
Open 6 PlaylistOpenNoMedia 再生リストは開いています。
(2) WindowsMediaPlayer1.controls.play;
Open 21 OpeningUnknownURL 不明な種類の URL を開いています。
Play 9 Transitioning 新しいメディアを準備中です。
Open 12 MediaOpening メディアは取得済みで、現在開いているところです。
(3) controls.playから戻る。
Open 13 MediaOpen メディアは現在開いています。
Play 1 Stopped 現在のメディア クリップの再生が停止されています。
(1) 三曲目読み込み
WindowsMediaPlayer1.URL := ListBox1.Items[3];
Open 1 PlaylistChanging 新しい再生リストが読み込まれようとしています。
Play 9 Transitioning 新しいメディアを準備中です。
Open 7 PlaylistChanged 新しい再生リストが currentPlaylist に割り当てられました。
Open 6 PlaylistOpenNoMedia 再生リストは開いています。
Play 10 Ready 再生を開始する準備ができています。
(2) WindowsMediaPlayer1.controls.play;
(3) controls.playから戻る。
Open 8 MediaChanging 新しいメディアが読み込まれようとしています。
Play 9 Transitioning 新しいメディアを準備中です。
Open 6 PlaylistOpenNoMedia 再生リストは開いています。
--- ここで止まる ---

これではどうにもうまくないので色々試してみてメディアの終わりに来た時点で次の曲を読み込ませるようにしました。
PlayStateChangeイベントでwmppsMediaEndedをつかまえます。

procedure Form1.Button1Click(Sender: TObject);
begin
  myDebug.gpcDebugAdd('(1)');           //--- (1)
  WindowsMediaPlayer1.URL := ListBox1.Items.[0];
  myDebug.gpcDebugAdd('(2)');           //--- (2)
  WindowsMediaPlayer1.controls.play;
  myDebug.gpcDebugAdd('(3)');           //--- (3)
end;

procedure Form1.WindowsMediaPlayer1PlayStateChange(Sender: TObject; NewState: Integer);
begin
  myDebug.gpcDebugAdd('Play', NewState);
  if (NewState = wmppsMediaEnded) then begin
    myDebug.gpcDebugAdd('(1)');           //--- (1)
    ListBox1.ItemsIndex := ListBox1.ItemIndex + 1;
    WindowsMediaPlayer1.URL := ListBox1.Items.[ListBox1.ItemIndex];
    myDebug.gpcDebugAdd('(2)');           //--- (2)
    WindowsMediaPlayer1.controls.play;
    myDebug.gpcDebugAdd('(3)');           //--- (3)
  end;
end;

状態 説明
(1) WindowsMediaPlayer1.URL := ListBox1.Items[0];
Open 1 PlaylistChanging 新しい再生リストが読み込まれようとしています。
Play 9 Transitioning 新しいメディアを準備中です。
Open 7 PlaylistChanged 新しい再生リストが currentPlaylist に割り当てられました。
Open 6 PlaylistOpenNoMedia 再生リストは開いています。
Play 10 Ready 再生を開始する準備ができています。
(2) WindowsMediaPlayer1.controls.play;
Open 21 OpeningUnknownURL 不明な種類の URL を開いています。
Play 9 Transitioning 新しいメディアを準備中です。
Open 12 MediaOpening メディアは取得済みで、現在開いているところです。
(3) controls.playから戻る。
Open 13 MediaOpen メディアは現在開いています。
Play 3 Playing 現在のメディア クリップは再生中です。
--- 再生中 ---
Play 8 MediaEnded メディアの再生が完了し、最後の位置にあります。
Play 9 Transitioning 新しいメディアを準備中です。
Play 1 Stopped 現在のメディア クリップの再生が停止されています。
(1) WindowsMediaPlayer1.URL := ListBox1.Items[1];
Open 1 PlaylistChanging 新しい再生リストが読み込まれようとしています。
Play 9 Transitioning 新しいメディアを準備中です。
Open 8 MediaChanging 新しいメディアが読み込まれようとしています。
(二回目以降なので8)
Open 6 PlaylistOpenNoMedia 再生リストは開いています。
Play 10 Ready 再生を開始する準備ができています。
Open 7 PlaylistChanged 新しい再生リストが currentPlaylist に割り当てられました。
Open 6 PlaylistOpenNoMedia 再生リストは開いています。
(2) WindowsMediaPlayer1.controls.play;
Open 21 OpeningUnknownURL 不明な種類の URL を開いています。
Play 9 Transitioning 新しいメディアを準備中です。
Open 12 MediaOpening メディアは取得済みで、現在開いているところです。
(3) controls.playから戻る。
Play 10 Ready 再生を開始する準備ができています。
--- ここで止まる ---

結局途中で止まってしまうのですが二度読みすることはないのでまだましかと。
後は色々フラグを立てたりしてやってみたのですが、タイマーを使って逐一状態を監視して再生させてしまうというのが一番確実でいいようです。


その後。
Readyで止まるのならばwmppsReadyをつかまえて、そこへplayメソッドを追加すれば良いのではと思い至りました。
ところがたんにplayメソッドを付け加えただけでは連続再生はできるもののエラーが出てしまいます。
色々やってみた結果currentMediaが有効であればplayするようにしたらエラーも出ず連続再生もOKとなりました。

procedure Form1.WindowsMediaPlayer1PlayStateChange(Sender: TObject; NewState: Integer);
begin
  if (NewState = wmppsMediaEnded) then begin
    ListBox1.ItemsIndex := ListBox1.ItemIndex + 1;
    if (ListBox1.ItemsIndex = ListBox1.Count) then ListBox1.ItemsIndex := 0;  //ループ再生
    WindowsMediaPlayer1.URL := ListBox1.Items.[ListBox1.ItemIndex];
    WindowsMediaPlayer1.controls.play;
  end else if (NewState = wmppsReady) then begin
    if (Assigned(WindowsMediaPlayer1.currentMedia)) then begin
      WindowsMediaPlayer1.controls.play;
    end;
  end;
end;

再びその後。
この方法ではうまくいかない場合もあるようでした。

私のやり方だと*.cdaが連続再生されませんでした。
ということでOnPlayStateChangeイベントだけでなくOnOpenStateChangeイベントでも止まるところに逐一controls.palyを仕込んでいってみたのですが、環境によって動いたり動かなかったりという状況のようでした。
で、(おなじみの)「真琴」さんと「春子」さんのかけあいでアドバイスしていただいたようにPostMessageを使ってみたところだいぶ良い感じに仕上がりました。
ただ提示してもらったコードだとStopボタンを押しても止まらないのと、うちの環境だと*.flvファイルが無限ループに陥りハングアップしてしまいます。

ということでフラグを使って書き換えてみた結果です。

まず停止ボタンを押しても止まらずに次の曲の再生が始まってしまうことへ対処します。
wmppsMediaEndedを通ったかどうかのフラグを加えます。
これによりユーザーが停止ボタンを押して途中で再生をやめようとしたのかそれともメディアの最後まできたのでwmppsStoppedにきたのか判断できるようになります。

  private
    { Private 宣言 }
    F_iPlayIndex: Integer;
    F_bMediaEnded: Boolean;  //「Stop」ボタンを押して停止させようとしたのかそうでないのかの判定用
    procedure F_Play;
    procedure AppMessage(var Msg: TMessage); message WM_APP;
  public
    { Public 宣言 }
  end;


    ...


procedure TForm1.F_Play;
begin
  if (F_iPlayIndex < ListBox1.Count) then begin
    ListBox1.ItemIndex := F_iPlayIndex;
    WindowsMediaPlayer1.URL := ListBox1.Items[F_iPlayIndex];
    WindowsMediaPlayer1.controls.play;
    Inc(F_iPlayIndex);
    if (F_iPlayIndex >= ListBox1.Count) then F_iPlayIndex := 0; //ループ再生
  end;
end;

procedure TForm1.AppMessage(var Msg: TMessage);
begin
  //PostMessageされたときの処理
  F_Play;
end;

procedure TForm1.WindowsMediaPlayer1PlayStateChange(Sender: TObject; NewState: Integer);
begin
  case NewState of
    wmppsMediaEnded: begin
      F_bMediaEnded := True;  //メディアの終わりまできた
    end;
    wmppsStopped: begin
      //wmppsMediaEndedを通ったかどうかで「Stop」ボタンを押したのかどうかの判定をする
      if (F_bMediaEnded) then begin
        //wmppsMediaEndedを通ったので停止させず連続再生させる
        PostMessage(Handle, WM_APP, 0, 0);
      end;
      F_bMediaEnded := False;
    end;
  end;
end;

これでユーザーが「Stop」ボタンを押した場合wmppsMediaEndedは通らないのでPostMessageの処理には入らずに再生は停止します。

次に*.flvファイルが無限ループに陥ることへ対処します。
その前に、実はwmppsMediaStoppedでPostMessageを行った場合、今まで連続再生させようとしてもwmppsReadyで止まっていたものが止まらずにちゃんと再生されるようになりました。
ということで*.cdaや*.wma、*.mp3、*.wmvなどの再生であれば上記のコードでOKです。
しかし*.flvなどはOnMediaErrorイベントが起きてwmppsReadyで止まります。
そこでwmppsReadyでもwmppsStoppedと同じようにPostMessageを行うと、URLにファイルをセットしなおすためか再生が始まらずそれどころか無限ループに陥りハングアップします。
PostMessageの処理でwmppsStoppedから呼ばれた場合とwmppsReadyから呼ばれた場合で処理を分けwmppsReadyからのときはURLにファイルをセットせずcontrols.playだけ行うようにすればちゃんと再生できるようにはなります。
とはいえ終了時にエラーが出てしまったりしてあまりいい感じではありません。
ということでwmppsReadyではPostMessageではなく単純にcontrols.playとするだけの方が良いような感じです。

procedure TForm1.WindowsMediaPlayer1PlayStateChange(Sender: TObject; NewState: Integer);
begin
  case NewState of
    wmppsMediaEnded: begin
      F_bMediaEnded := True;  //メディアの終わりまできた
    end;
    wmppsStopped: begin
      //wmppsMediaEndedを通ったかどうかで「Stop」ボタンを押したのかどうかの判定をする
      if (F_bMediaEnded) then begin
        //wmppsMediaEndedを通ったので停止させず連続再生させる
        PostMessage(Handle, WM_APP, 0, 0);
      end;
      F_bMediaEnded := False;
    end;
  end;
  wmppsReady: begin
    if (Assigned(WindowsMediaPlayer1.currentMedia)) then begin
      WindowsMediaPlayer1.controls.play;
    end;
  end;
end;

これだけで充分な気もするのですが、フォルダ中のファイルリストを取得してListBox1のItemsにセットして再生させてみたところサムネイルファイルのThumbs.dbを読み込んだところでハングアップしてしまいました。
他にも*.iniや*.txtなど、再生できないファイルはハングアップします。
ということでそれらのファイルへ対処するためにF_iErrorCountというフラグを使います。
またメディアエラーを捕まえるためOnMediaErrorイベントを利用します。

    procedure WindowsMediaPlayer1PlayStateChange(Sender: TObject; NewState: Integer);
    procedure WindowsMediaPlayer1MediaError(Sender: TObject; const pMediaObject: IDispatch);
  private
    { Private 宣言 }
    F_iPlayIndex: Integer;
    F_bMediaEnded: Boolean;
    F_iErrorCount: Integer;

    procedure F_Play;
    procedure AppMessage(var Msg: TMessage); message WM_APP;
  public
    { Public 宣言 }
  end;


  ...


procedure TForm1.WindowsMediaPlayer1MediaError(Sender: TObject; const pMediaObject: IDispatch);
begin
  Inc(F_iErrorCount);
end;

procedure TForm1.WindowsMediaPlayer1PlayStateChange(Sender: TObject; NewState: Integer);
begin
  case NewState of
    wmppsMediaEnded: begin
      //「Stop」ボタンを押して停止させようとしたのかそうでないのかの判定のために必要
      F_bMediaEnded := True;
    end;
    wmppsStopped: begin
      //wmppsMediaEndedを通ったかどうかで「Stop」ボタンを押したのかどうかの判定をしている
      if (F_bMediaEnded) then begin
        //wmppsMediaEndedを通ったので停止させず連続再生させる
        PostMessage(Handle, WM_APP, 0, 0);
      end;
      F_bMediaEnded := False;
    end;
    wmppsReady: begin
      //*.flvファイルなどは2回エラーが起き、最初だけplayさせればOK
      if (F_iErrorCount = 1) then begin
        if (Assigned(WindowsMediaPlayer1.currentMedia)) then begin
          WindowsMediaPlayer1.controls.play;
        end;
      end else begin
        F_iErrorCount := 0;
      end;
    end;
  end;
end;

F_iErrorCountが1のときだけcontrols.playを呼ぶようにしています。
*.flvなどであれば最初のメディアエラーでcontrols.playを呼べば再生が始まります。
それ以外の*.txtファイルなどはcontrols.playを呼んでも再生が始まりません。
2回目のエラーではcontrols.playは呼ばれないのでそのまま再生は停止します。


procedure TForm1.ListBox1DblClick(Sender: TObject);
begin
  F_iPlayIndex := ListBox1.ItemIndex;
  F_Play;
end;

procedure TForm1.F_Play;
begin
  if (F_iPlayIndex < ListBox1.Count) then begin
    ListBox1.ItemIndex := F_iPlayIndex;
    WindowsMediaPlayer1.URL := ListBox1.Items[F_iPlayIndex];
    WindowsMediaPlayer1.controls.play;
    Inc(F_iPlayIndex);
    if (F_iPlayIndex >= ListBox1.Count) then F_iPlayIndex := 0;  //ループ再生
  end;
end;

procedure TForm1.AppMessage(var Msg: TMessage);
begin
  F_Play;
end;

procedure TForm1.WindowsMediaPlayer1MediaError(Sender: TObject; const pMediaObject: IDispatch);
begin
  Inc(F_iErrorCount);
end;

procedure TForm1.WindowsMediaPlayer1PlayStateChange(Sender: TObject; NewState: Integer);
begin
  case NewState of
    wmppsMediaEnded: begin
      //「Stop」ボタンを押して停止させようとしたのかそうでないのかの判定のために必要
      F_bMediaEnded := True;
    end;
    wmppsStopped: begin
      //wmppsMediaEndedを通ったかどうかで「Stop」ボタンを押したのかどうかの判定をしている
      if (F_bMediaEnded) then begin
        //wmppsMediaEndedを通ったので停止させず連続再生させる
        PostMessage(Handle, WM_APP, 0, 0);
      end;
      F_bMediaEnded := False;
    end;
    wmppsReady: begin
      //*.flvファイルなどは2回エラーが起き、最初だけplayさせればOK
      if (F_iErrorCount = 1) then begin
        if (Assigned(WindowsMediaPlayer1.currentMedia)) then begin
          WindowsMediaPlayer1.controls.play;
        end;
      end else  begin
        F_iErrorCount := 0;
      end;
    end;
  end;
end;

2009-03-19: アドバイスに従いPostMessageを使ったものに書き換え。
2008-11-12: wmppsReadyにcontrols.playを追加で連続再生OKを追加。