[BlueLeaf1336]> PROGRAM>

TWindowsMediaPlayer - 003 - 再生画面のサイズを Form のサイズに合わせる

historyTOP

2007/11/13:作成
2007/11/15:Form のリサイズ完了を待って再生画面のサイズを調整するように変更
2007/11/29:最低限雰囲気がわかるように調整
2007/12/19:解決
2008/01/01:さらに解決

overviewTOP

続きです。前の話はTWindowsMediaPlayer - 002で。
その前の話はTWindowsMediaPlayer - 001で。

参考サイトTOP

前回の敗北ポイントTOP

3年半も前の話ですが

フォームサイズの変更に、TWindowsMediaPlayerが全くコレッポッチもそぶりさえ見せずについてきません。嫌過ぎ。

と書きおいてほったらかしにしていたのは、次のような状況でした。

[1] 設計時に、WindowsMediaPlayer の部品を Form に落として、Align を alClient にします。すると Form いっぱいのサイズに広がります。
また、その状態で実行しても、ちゃんと画面いっぱいです。

[2] ところが、実行中に Form のサイズを変更すると残念なことが起こります。Align を alClient にしているのに Form のサイズに再生画面が追いついてこないのです。

そんなプレイヤー見たことないってんで、放置プレイです。

それとは別に

設計時、TWindowsMediaPlayerをダブルクリックすると、よさげな設定画面が表示されます。今日初めて気づきました。

とも書いています。3年半前に。これが「よさげな設定画面」です。

この設定画面にある「再生オプション」の中の「ウィンドウに合わせる」というのがひょっとしたらさっきの問題を解決する方法か?と期待してみたのですが違いました。どう違うのかというと...

[1]まず「ウィンドウに合わせる」を「チェックしない」時がこんな感じです。といっても再生内容はキャプチャできてないのでナンですが、うっすらと周りと再生領域に色の違いがあります。
これは多分 wmv ファイルのオリジナルサイズになっているんだと思います。

[2]次が「ウィンドウに合わせる」を「チェックした」状態です。再生領域がいっぱいまで広がっています。

[3]でも...再生中(に限らず起動後のすべての時点で)に Form のサイズを変更してみると、再生画面はついてきません。

つまり、「ウィンドウに合わせる」というのは、TWindowsMediaPlayer コンポーネントの再生画面いっぱいにオリジナルサイズを無視して、縦横比率は維持したままの感じで、適当に拡大してくれるものであって、TWindowsMediaPlayer 自体のサイズには関係ないものということになります。ちょっと考えるとそりゃそうかも、と思う結論です。

じゃあ、と3年前とかそれ以降とかに考えたわけです。Form のサイズが変わるたびに TWindowsMediaPlayer コンポーネントを作り直せば?

具体的には FormCreate でまず作る。次に、FormResize で Player を Free してまた作る。そのたびに、Align は alClient にすれば、TWindowsMediaPlayer 自体は Form いっぱいになるやん。で、ついでに設定画面でやった「ウィンドウに合わせる」にチェックを入れるのに相当するコードをピピッと書いてやれば、それなりになるんちゃう?

で、「ウィンドウに合わせる」というのが、多分ネット上で見つけたように記憶してますが StretchToFit というプロパティのことらしい所まではたどり着きました。後はやるだけなんですが、この辺で力尽きたのを覚えてます。

しかも、飽きたわけじゃなく、単純にできなかったわけで。

敗因を整理してみるTOP

無駄話が過ぎたので要点がよくわからなくなってきたので整理しておくと、「Form のサイズを変更したときに、再生画面がそれなりに Form のサイズについてきてほしい」問題に対してやるべきことは 2 つです。

  1. Form のサイズに TWindowsMediaPlayer コンポーネントのサイズを合わせる
  2. TWindowsMediaPlayer コンポーネントのサイズに再生領域のサイズを合わせる

これだけのことが、なぜ問題になるかというと

  1. いったん作られた TWindowsMediaPlayer コンポーネントはなぜかサイズが変わらない
  2. 「ウィンドウに合わせる」にチェックを入れる必要がある

で、解決方法として

  1. サイズ変更が必要になるたびに TWindowsMediaPlayer コンポーネント を作り直す
  2. そのたびに「ウィンドウに合わせる」にチェックを入れる

そして敗因は、設計時には簡単にできる「ウィンドウに合わせる」にチェックを入れることが実行時には簡単じゃないということです。

どうもそれだけじゃなかったような気もしますが、そういうことです。

実行時に「ウィンドウに合わせる」にチェックを入れるTOP

が、いきなり解決策がネットで見つかりました。それが参考サイトとして、このページの最初に上げたページですが、Delphi Q & A 掲示板 の中で見つかりました。少し長いですがモロなので引用させていただきます。#略したり改行したりしてます。

WindowsMediaPlayer1いっぱいの再生表示するには?

駆け出し
2007/11/09(金) 12:02:54 
(略)
WindowsXP(Delphi 6 Personal)です。

TWindowsMediaPlayer のコンポーネントの取り込みを
ActiveX コントロールの「Windows Media Player (Version 1.0)」
で取り込みました。
(略)
WindowsMediaPlayer1の画面いっぱいのサイズで再生表示するには
(略)

DEKO
2007/11/09(金) 12:11:15 
WindowsMediaPlayer1.StretchToFit := True;
でよかったと思います。

駆け出し
2007/11/10(土) 00:11:41 
(略)
WindowsMediaPlayer1.StretchToFit := True;
を表示命令の前に記述するのですがStretchToFitが
「未定義の識別子」になります。
(略)

DEKO
2007/11/10(土) 03:25:08 
(略)
念の為にDelphi6で新規にwmp.dllをからWMPLib_TLBを作ってみました。
これには確かにStretchToFitプロパティがありませんでした
(Vista/WMP11)。

私の環境で過去に取り込んだタイプライブラリ(XP/WMP9)でインストール
されたTWindowsMediaPlayer(D5/D7)にはstretchToFitプロパティがあります。
(略)

問い合わせ
2007/11/10(土) 16:43:46 
(略)
uses WMPLib_TLB;

procedure TForm1.FormCreate(Sender: TObject);
var
    iCore: IWMPCore;
    iWmpD: IWMPPlayer4Disp;
begin
    (略)
    iCore := WindowsMediaPlayer1.ControlInterface;
    if iCore <> nil then begin
        // 問い合わせ
        iCore.QueryInterface(IWMPPlayer4Disp, iWmpD);
        if iWmpD <> nil then begin
            iWmpD.StretchToFit := True;
            iWmpD.URL := 'http://hoge.hoge.com/ほにゃ.wmv';
        end;
    end;
end;
(略)
真琴:「ハルコさん、あと、フォームのサイズ変更に合わせて再生画面のサイズも変えられたらいいね」
春子:「ん?…モチロン、それも出来るけど…」
真琴:「それも一行で出来ちゃうの?」
春子:「一行じゃムリよ、でも、そんなにムズカシクはないから、また今度教えてあげるョ」
(略)

まさにこれです。「問い合わせ」さんの書かれているコードがまさにそれ。やっと続きを書ける目処が立ちました。ただこの3年半の間に OS が WindowsXP に変わったことや、Window Media Player 自体が Ver.11 になったことと関係あるのかもしれないのですが。

ていうか「また今度教えてあげるョ」といわず今教えてほしいです。

ところで、「問い合わせ」さんですが、「Delphi Q & A 掲示板」でよく見かけるように思います。長くなるので引用時に省略しましたが、登場人物 2 人のユルく聞こえる会話で問題点をビシッと表現して、ガシッと解決するという独特の書き込みが、読んでいて楽しいです。確認したわけじゃないので同一人物の書き込みかどうかわかりませんが「問い合わせ」ライブラリ(仮) を作れば、実用的な Tips 集ができてしまう気がします。

できるかどうか試してみるTOP

「また今度教えてあげるョ」が頭の中をぐるぐる回りますが

  1. サイズ変更が必要になるたびに TWindowsMediaPlayer コンポーネント を作り直す
  2. そのたびに「ウィンドウに合わせる」にチェックを入れる

をそのまま書いてみたら、まったく思ったとおりになりました。非実用的なところも含めて思ったとおりです。

[1] 起動したところ。ここまでは部品をおいただけでも実現できます。

[2] フォームを横に広げてみました。ちゃんと TWindowsMediaPlayer のサイズが Form のサイズについてきてます。幅が広くなっただけなので、再生領域はそのままのサイズです。縦横比が維持されているとすると正しい動作です。

[3] さらに縦に広げてみました。TWindowsMediaPlayer のサイズが Form のサイズについてきているし、再生画面も広がってます。

さて、先ほど「非実用的なところ」と書きましたが、サイズ変更の度に再生位置が振り出しに戻るというのが決定的な点です。もちろん同時にファイルの読み直しも発生します。

それはそれとして、第 1 歩といっていいと思います。

できるかどうか試してみたソースコードTOP

以下のコードでは、再生を指示する部分がないんですが、TWindowsMediaPlayer を部品としておいたときにダブルクリックで表示される設定画面の「再生オプション」で「自動的に開始する」にチェックが最初から入っていることが関係していると思います。URL を指定して読み込みが完了(?)したら再生も開始するようです。

ここでは、自前で Create していますが URL を指定するだけでちゃんと再生されるので、上記の再生オプション(自動再生) はデフォルト値のようです。

unit Unit1;

interface

uses
  Windows, Messages, Variants, Controls, Forms, WMPLib_TLB;

type
  TForm1 = class(TForm)
    procedure FormResize(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
  private
    //  TWindowsMediaPlayer を部品を置かずにメンバーとして定義
    FPlayer: TWindowsMediaPlayer;
    //  TWindowsMediaPlayer を作り直す
    procedure ReCreatePlayer();
  public
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

//-----------------------------------------------------------------------------
//  TWindowsMediaPlayer を作り直す
//  http://hpcgi1.nifty.com/MADIA/DelphiBBS/wwwlng.cgi?print+200711/07110026.txt
//  WindowsMediaPlayer1いっぱいの再生表示するには?
procedure TForm1.ReCreatePlayer();
var
    LCore: IWMPCore;
    LPlayer4Disp: IWMPPlayer4Disp;
begin
    //  作ってなければ
    if not Assigned(FPlayer) then
    begin
        //  作る
        FPlayer := TWindowsMediaPlayer.Create(Self);
        //  TWindowsMediaPlayer コンポのサイズを Form のサイズに合わせる
        FPlayer.Align := alClient;
        //  Align の後に Parent を設定(逆だとだめ)
        FPlayer.Parent := Self;
        //  プレイヤーのインターフェースを取り出す(?)
        LCore := FPlayer.ControlInterface;
        if Assigned(LCore) then
        begin
            //  StretchToFit を持っているインターフェースへの参照を取り出す
            LCore.QueryInterface(IWMPPlayer4Disp, LPlayer4Disp);
            if Assigned(LPlayer4Disp) then
            begin
                //  で、設定する
                LPlayer4Disp.StretchToFit := True;
            end;
        end;
    end;
end;

//-----------------------------------------------------------------------------
//  フォーム作成時
procedure TForm1.FormCreate(Sender: TObject);
begin
    FPlayer := nil;
end;

//-----------------------------------------------------------------------------
//  フォーム破棄時
procedure TForm1.FormDestroy(Sender: TObject);
begin
    //  プレイヤーを破棄する
    FPlayer.Free();
    FPlayer := nil;
end;

//-----------------------------------------------------------------------------
//  フォームサイズ変更字
procedure TForm1.FormResize(Sender: TObject);
begin
    //  プレイヤーを破棄する
    FPlayer.Free();
    FPlayer := nil;
    //  作る
    ReCreatePlayer();
    //  再生するファイルを指定する
    FPlayer.URL := '再生するファイル名.wmv';
    //  表示する
    FPlayer.Show();
end;

end.

今後の予定TOP

すでに書きましたが、Form のサイズ変更のたびに再生位置がリセットされてしまうのは致命的です。致命的ですが、回避方法も多分簡単です。作り直す前に「再生中なら再生位置を記録」しておいて、作り直した後で「再生位置を指定する」だけでいいと思います。

それよりも、「また今度教えてあげるョ」が気になります。普通のプレイヤーを考えると、サイズ変更のたびにそんなことをやっているわけがなく、絶対に何かの方法があるはずで。

実際、TWindowsMediaPlayer コンポーネントのサイズさえうまいこと変更できれば、後は StrechToFit がやってくれるはずです。つまり、TWindowsMediaPlayer コンポーネントのサイズをいったん作ってしまった後にどうやって変更するかを試行錯誤すればいいということになります。

ただ、この「読み直し」については自分に限っていえば、それほどの問題ではないんですが。というのも、3年前は違いましたが、今現在このプログラムを触っている目的は、テレビ放映された映画を DVD にコピーしたのを見たいがためです。

HDD プレイヤーにある「30秒スキップ」とか「15秒バック」などの CM スキップ機能をつけたいだけなんです。それも 本編と CM の間の何かの信号を捕らえるとかじゃなく単純なスキップです(DVD にとった後って HDD プレイヤーでも CM スキップ機能がなくなるようなので信号検地スキップは根本的に無理なのかも)。それさえあれば細かい調整なんてすることもなく、再生中に画面サイズを変更することもせず、黙ってええ子にして映画を見てるわけで。

結局のところ

の 2 方向のようです。

が、ほぼ 1 年前に録画しておいた「アポロ13」を今日見て、同じ DVD にコピーしておいた「ディープインパクト」を数分見たところで、30秒スキップ機能つきのプレイヤーつくろうやと思い立ったので、やっぱり 1 つめの実用目的の方を選びそうです。

20071113WindowsMediaPlayerTest.zip(178,455Bytes) テストに使ったソースコードと実行ファイルです。
※再生する動画ファイル名を直接書いているので、そのまま実行しても面白くありません。

Form のリサイズ完了を待って再生画面のサイズを調整するように変更TOP

上にも書いたとおり、映画の場合いったん見始めたらそうそう頻繁に画面サイズを変更することはないんですが、それでも画面をずるずると大きくするときにちかちかするのはかなり気持ちが悪いです。

WindowsMediaPlayer がディスク容量いっぱいまで録画した DVD のファイル、Pioneer の HDD レコーダーから DVD に 2 本の映画を 1 つの DVD にダビングすると 1 つのファイルになるので、まさに 4.2GB といったサイズになりますが、を何度も読み直すというのはちょっとよくないなあと。

実際、FormResize にカウンターを仕込んでタイトルバーに表示するようにしてみると、右下端をつかんでサイズ変更してみるとすごい勢いでカウンタがあがります。

そういうわけで、困ったときの Google 頼みと行きたいところですが、今インターネットが見られないので、ダウンロードした Delphi メーリングリストで検索してみると...見つかりました。

[Delphi-ML:20299] Windowのリサイズイベントについて教えてください。

というタイトルで、リサイズし終わった時に処理したいと尋ねてる方がいました。そして、希望通りに動作する回答があって試してみました。こんな感じです。

(略)
type
  TForm1 = class(TForm)
    (略)
  private
    //  リサイズが終了した瞬間を捉える
    FResized: Boolean;
    procedure OnResized();
    procedure WMEXITSIZEMOVE(var Message: TMessage); message WM_EXITSIZEMOVE;
    (略)
  end;

(略)
implementation
(略)

//-----------------------------------------------------------------------------
//  リサイズが終了した瞬間を捉える
//  [Delphi-ML:20299] Windowのリサイズイベントについて教えてください。
procedure TForm1.WMEXITSIZEMOVE(var Message: TMessage);
begin
    if FResized then OnResized();
    inherited;
end;

//-----------------------------------------------------------------------------
//  リサイズ完了イベント
procedure TForm1.OnResized;
begin
    //  プレイヤーを作り直す
    (略)
    //  この関数の呼び出される回数を数えてみる
    Caption := IntToStr(StrToIntDef(Caption, 0) + 1);
    //  最後の1回を捉えるためのフラグOFF
    FResized := False;
end;

//-----------------------------------------------------------------------------
//  フォームサイズ変更時
procedure TForm1.FormResize(Sender: TObject);
begin
    //  最後の1回を捉えるためのフラグON
    FResized := True;
end;
(略)

肝は WM_EXITSIZEMOVE が飛んできたときが、リサイズし終わってマウスから手を離したとき(らしい)という点です。

[1] まず起動時。キャプションに 「1」 とあります。実は上のような抑制を行うと OnResized 関数は呼び出されません。FormResize イベントは呼び出されるんですが。ということで FormShow で、自分で OnResized 関数を呼んでいます。

[2] 次が画面の右下端をマウスでつかんでサイズを変更しているところです。マウスをキャプチャし忘れたので不思議な画面になっていますが、実際には Form サイズを変更中で、FormResize イベントはバキバキ発生しています。
FormResize イベントで再生画面を作り直していると、キャプションのカウンタが一気に進むところですが、抑制しているのでカウンタは「1」のままです。
ただ、見かけ上少しだけ自然にするために背景を黒くしたりしています。

[3] でマウスから手を離してサイズ変更をやめると、再生画面が Form のサイズにフィットします。ついでにキャプションのカウンタも「2」に進むと。いい感じです。

しかし、無問題かというとそうでもないです。キャプションにある「最大化ボタン」が死んでいるのと、わざわざ「Maximize」ボタンをつけているのがその問題が原因なわけで。
どうも、WM_EXITSIZEMOVE メッセージは、キャプションバーをダブルクリックしたり、キャプションバー右端にある最大化ボタンで画面を最大化すると飛んでこないんです。なので、最大化して映画見る! と意気込んで見ても残念なことに再生画面はちっちゃいまま。今のところどうにもなりませんでした。

じゃあ、ってんで、自分で最大化ボタンをつけたわけで。

でもまあこれでいいことにします。

20071115WindowsMediaPlayerTest.zip(184,178Bytes) テストに使ったソースコードと実行ファイルです。
今回は少し反省して、再生画面を触るとファイル選択画面を開くようにしています。サイズ変更後の復帰処理はまだ入れてないので、その度に一切を忘れます。しかも、ファイル選択画面を開くようにしたことで、今度は WindowsMediaPlayer 標準のメニューさえ出ません。仕様ですけど。

ちなみにファイル再生中に画面サイズを変更してみると、ちゃんと再生はそのまま続きます。普通の Player ならさらに再生画面のサイズも追従するんですけど、それはそれ、これはこれ。しょぼいもんはしょぼいとして受け止めることにします。

最低限雰囲気がわかるように調整TOP

実行画面です。相変わらず再生中の画面はキャプチャできてません。

前回自分で使うのにいるかも、と思っているものを書きましたが少しだけ組み込んでみました。

使ってみて驚いたんですが、「30秒巻き戻し・15秒巻き戻し・15秒早送り・30秒早送り」の便利さが思った以上でした。これならもう少し進めてみようと思わないでもないです。

ただ、用途はほぼ映画限定って書いたばかりですが、こうなってくると欲が出てきて数分から30分程度の動画を見たりもしたくなるわけで...

そうなると、プレイリストに代わるものが必要になるわけで。でも今のところ最強のプレイリストは、多分 APlayer のコンボボックスタイプなわけで。うーん。どっちにしてもこれ以上は PROBLEMS でやると思います。

20071129PoorPlayer.zip(274,401Bytes) テストに使ったソースコードと実行ファイルです。

解決TOP

閑古鳥の鳴いているこのサイトのBBSに書き込みがありました。で、解決です。

色々やってみて下のようにApplication.Handleにフォーカスを移してから
自身に戻すことでもサイズ変更できることがわかりました。
ちらつきも少なくなりましたし動作も軽くなりました。

WindowsMediaPlayer1.Enabled := False;
WindowsMediaPlayer1.Enabled := True;
Windows.SetFocus(Application.Handle);
Windows.SetFocus(Self.Handle);

これなら、まぁ普通に使える感じがします。

試してみましたが、まったくそのとおり。普通に使えます。

そういうわけで、終了。やっぱりなんか手があると思ってましたが、まさかこんな解決法とはびっくりです。

また、別の方から MediaPlayer1.DisplayRect := R; じゃぁだめ?との書き込みもいただきました。結果としてはだめだったんですが、Delphi標準の MediaPlayer コントロールの場合はこれでいけそうな感じです。もはやこのパソコンには標準の MediaPlayer コントロールが入ってないので試せません。

いずれにしても、おふたりには感謝です。

さらに解決TOP

さらに、このサイトのBBSに書き込みがありました。ern さんという方でした。ほぼ最強です。どうやってこの答えにたどりつかれのかまったく不明ですが...。とりあえず Google で検索 してみましたが、日本語のサイトは皆無でした(2007/12/末)。

procedure TForm1.FormResize(Sender: TObject);
const
    IID_IOleInPlaceObject: TGUID = '{00000113-0000-0000-C000-000000000046}';
var
    IOIPObj: IOleInPlaceObject;
    WMPRect: TRect;
begin
    IDispatch(FPlayer.OleObject).QueryInterface(IID_IOleInPlaceObject, IOIPObj);
    SetRect(WMPRect, 0, 0, PnlMain.Width, PnlMain.Height);
    IOIPObj.SetObjectRects(WMPRect, WMPRect);
end;
ただ、QueryInterface を使っている辺りが、このページの最初のほうに引用した「問い合わせ」さんの書き込みに似ています。「また今度教えてあげるョ」というのはこれのことだったのかとも思わせます。ついでにいうと、IOIPObj <> nil を確かめておいたほうが安全のようです。

EOFTOP