Duncan Mackenzie
Microsoft Developer Network
June 26, 2003
日本語版最終更新日 2003 年 9 月 17 日
要約: Duncan Mackenzie が、Remoting および Pocket PC を使用して Windows Media® Player 9 対応の単純なリモート コントロールを作成する方法について説明します。
適用対象:
Microsoft® Visual Basic® .NET
この記事のソース コードをダウンロードする (英語)
コントロール マニア
リモート コントロールは、Microsoft Windows Forms コントロールと比べると見劣りしますが、手元のボタンを押してなんらかの操作をすることを可能にしてくれます。これは本当に便利なものです。以前に音楽再生システムを構築したとき、リモート コントロールのサポートがどうしてもほしかったので、IRMan をシリアル ポートに接続して IR サポートを組み込みました。その後、さらに上のレベルに進むことにしました。一方向の IR 通信ではもはや満足できなくなったためです。それよりも、システムと通信し、自分がシステムを見ることができないときでも制御を可能にするような十分な情報を与えてくれる、フルカラーのリモート コントロールを望むようになりました。そうこうしているうちに、Microsoft .NET が自分の望みを叶えてくれることがわかりました。Microsoft .NET Remoting と Microsoft .NET Compact Framework を使用すれば、Pocket PC を非常に強力なリモート コントロール デバイスとしてワイヤレス ネットワーク上で機能させることができるのです。

図 1. Pocket PC リモート コントロールのメイン ページ
ネットワーク上の音楽制御システム (NMCS) のデザイン
最終的な目標は、自分の音楽システムで機能するリモート コントロールを構築することでしたが、誰のマシンでも実行できる Microsoft Visual Basic .NET アプリケーション (Microsoft Windows Media Player コントロールが Form に組み込まれているもの) で機能する単純なコントロールを作成した方が、多くの人の役に立つと思ったのです。
Pocket PC で実行する .NET Compact Framework アプリケーションは、リモート処理を行い、メディア ライブラリに関する情報を取得し、標準セットの音楽コマンドを送受信することで、ホストと通信します。コマンドのセットは次のとおりです。
メディア情報
- Get Current (現在再生中の曲に関する情報をホストから取得します。)
- Get Artists (アーティストの一覧をホストのメディア ライブラリから取得します。)
- Get Albums (アルバムの一覧をホストのメディア ライブラリから取得します。)*
- Get Albums By Artist
- Get Songs By Album*
- Get Songs By Artist
- Get Album Cover For Song (特定の曲に固有のイメージを取得します。)
* これらのメソッドは実装はしたものの、サンプルでは使用しませんでした。
ナビゲーション コントロール
- Play Album | Artist | Song
- Stop、Pause/Play、Next/Previous Song
- Mute、Volume Up/Down
もちろん、さらに包括的なコマンド セットを実装することもできますが、上の機能のセットでとりあえず必要なことはすべて満たされます。この機能のセットを基本的な「仕様」として使用すれば、クライアントに取り組む前に、Windows Media Player を実行する完全なホスト アプリケーションを構築できます。
NMCS ホスト アプリケーションを作成する
ホスト アプリケーションとしては、Windows Media Player コントロールをホストするための単一のフォームを含む、Visual Basic .NET Windows アプリケーションを作成します。コントロールを適切に組み込むには、Jim Travis が記述したこの記事の指示に従う必要があります。この記事の手順には、Windows Media Player SDK に付属のプライマリ相互運用機能アセンブリ (PIA) の登録も含め、すべて従ってください。

図 2. Windows Media Player を組み込むことでオブジェクト モデルにアクセスできるようになる
コントロールをフォームに組み込むと、必要となる主要メソッドやプロパティにアクセスできるようになります。この記事では、Windows Media Player のオブジェクト モデルのしくみについて掘り下げた説明はしませんが、自分で作成した代表的なコード例をいくつか紹介します。これから、前述のコマンドをすべて実装する新しいクラスを 2 つ作成します。1 つはナビゲーション コントロールに対応するクラスで、もう 1 つはメディア情報を処理するクラスです。各コマンドのコードはかなり単純ですが、各クラスは Windows の Form に組み込まれている Windows Media Player のインスタンスにアクセスする必要があります。この後で紹介するように、初期化を必要としなければ、これらのクラスは簡単にリモート処理に使用できます。このため、Windows Media Player のインスタンスは別のクラスの Shared (静的) プロパティを使用して利用可能にします。
Imports AxMicrosoft.MediaPlayer.Interop
Public Class SharedMediaPlayer
Private Shared m_Player _
As AxWindowsMediaPlayer
Public Shared Property Player() _
As AxWindowsMediaPlayer
Get
Return m_Player
End Get
Set(ByVal Value _
As AxWindowsMediaPlayer)
m_Player = Value
End Set
End Property
End Class
アプリケーションが起動し、Windows Media Player のメイン インスタンスが作成されたら、Shared プロパティを設定し、アプリケーションが終了するまで使用可能にします。
Protected Overrides Sub OnLoad( _
ByVal e As System.EventArgs)
SharedMediaPlayer.Player = Me.wmp
SetUpRemoting()
End Sub
この共有プロパティを作成したら、次の 3 つのセットのコマンドのコーディングに移ることができます。
- プレーヤーの状態を制御するためのナビゲーション メソッド
- メディア ライブラリを操作するための情報メソッド
- 特定の音楽を再生するためのメソッド
3 セットのコードは互いに独立しているため、各コマンド セットは独自のクラスに分割しました。
ナビゲーション コントロールをプログラミングする
ナビゲーション コマンドは非常に簡単に作成できます。各コマンドは、Windows Media Player のオブジェクト モデルで既に公開されているメソッドに完全に対応しているためです。
Imports NMCSHost.SharedMediaPlayer
Imports Microsoft.MediaPlayer.Interop
Public Class NavigationalControls
Inherits MarshalByRefObject
Public Sub VolumeUp()
If Player.settings.volume < 100 Then
SharedMediaPlayer.Player.settings.volume += 1
End If
End Sub
Public Sub VolumeDown()
If Player.settings.volume > 0 Then
SharedMediaPlayer.Player.settings.volume -= 1
End If
End Sub
Public Sub Mute()
'ミュートを切り替えます
Player.settings.mute = Not Player.settings.mute
End Sub
Public Sub MoveNext()
Player.Ctlcontrols.next()
End Sub
Public Sub MovePrevious()
Player.Ctlcontrols.previous()
End Sub
Public Sub Play()
Player.Ctlcontrols.play()
End Sub
Public Sub Pause()
Player.Ctlcontrols.pause()
End Sub
Public Sub StopPlayer()
Player.Ctlcontrols.stop()
End Sub
End Class
ナビゲーション メソッドは Player オブジェクトの Controls プロパティを経由して公開されますが、.NET の Windows Media Player OCX を使用する場合は、そのプロパティの名前が自動的に Ctlcontrols に変更されます。これは、Microsoft ActiveX® Control ラッパーの既存のプロパティと競合するためです。
メディア ライブラリを利用する
情報コマンドのセットでは、メディア ライブラリに対してクエリを実行し、データを取得する必要があります。この時点では、情報をリモート アプリケーションに送信するときのデータ形式を決定することが大切です。この情報は、ネットワークを経由して送信する必要があるためです。ここでは、厳密に型指定された配列とカスタム オブジェクトを組み合わせて使用することに決定しました。文字列のリストを返すコマンド (GetAuthors など) では文字列配列を使用しますが、曲の一覧を返す場合は、カスタムの Song クラスを使用してその型の配列を渡します。
<Serializable()> Public Class Song
Public Authors As String
Public Title As String
Public Duration As String
Public Album As String
Public ContentID As String
Public CollectionID As String
Public TrackNumber As String
End Class
作者一覧を自分のメディア ライブラリから取得する場合は、getAttributeStringCollection 関数を使用すると便利ですが、ライブラリのサイズによっては非常に大きな値のセットが返される場合があります。カスタム文字列収集クラスがメディア ライブラリから返されますが、これらの値を ArrayList にコピーし、文字列の配列に変換してクライアントに返します。
Public Function GetAuthors() As String()
Dim mc As IWMPMediaCollection
mc = Player.mediaCollection
Dim artists As IWMPStringCollection _
= mc.getAttributeStringCollection("Author", "AUDIO")
Dim artistList As New ArrayList
For i As Integer = 0 To artists.count - 1
artistList.Add(artists.Item(i))
Next
Dim saArtists(artistList.Count - 1) As String
Return artistList.ToArray(GetType(String))
End Function
特定の作者またはアーティストのすべてのアルバムは、若干複雑なプロセスによって引き出すことができます。つまり、そのアーティストの曲をすべて取得し、一意のアルバム名をスキャンします。
Public Function GetAlbumsByArtist( _
ByVal ArtistName As String) As String()
Dim albums As New ArrayList
Dim songs As Song() = GetSongsByArtist(ArtistName)
For Each sng As Song In songs
If Not albums.Contains(sng.Album) Then
albums.Add(sng.Album)
End If
Next
Return albums.ToArray(GetType(String))
End Function
Public Function GetSongsByArtist( _
ByVal ArtistName As String) As Song()
Dim pl As IWMPPlaylist
pl = Player.mediaCollection.getByAuthor(ArtistName)
Return ConvertPlaylistToSongs(pl)
End Function
Private Function ConvertPlaylistToSongs( _
ByVal pl As IWMPPlaylist) As Song()
Dim songs As New ArrayList(pl.count - 1)
For i As Integer = 0 To pl.count - 1
songs.Add(GetItemAsSong(pl.Item(i)))
Next
Return songs.ToArray(GetType(Song))
End Function
Private Function GetItemAsSong( _
ByVal md As IWMPMedia) As Song
Try
Dim current As New Song
With current
If Not md.getItemInfo("WM/WMContentID") _
= "{00000000-0000-0000-0000-000000000000}" _
And Not md.getItemInfo("WM/WMContentID") _
= String.Empty Then
.Album = md.getItemInfo("WM/AlbumTitle")
.Authors = md.getItemInfo("Author")
.ContentID = md.getItemInfo("WM/WMContentID")
.Duration = md.durationString
.Title = md.getItemInfo("Title")
.TrackNumber = md.getItemInfo("WM/TrackNumber")
End If
End With
Return current
Catch ex As Exception
MsgBox(ex.Message)
Return Nothing
End Try
End Function
MediaInformation クラスに含まれている興味深い関数の 1 つに GetImage があります。この関数は、ContentID をパラメータとして受け取り、適切なメディア項目をライブラリから検索し、アルバムのイメージを探します。アルバムのイメージが見つかった場合は、バイト配列に変換され、Base64 によってエンコードされて、文字列として返すことができるようになります。
Public Function GetImage( _
ByVal ContentID As String) As String
Dim md As IWMPMedia
Dim ImagePath As String
md = Player.mediaCollection.getByAttribute( _
"WM/WMContentID", ContentID).Item(0)
ImagePath = IO.Path.Combine( _
IO.Path.GetDirectoryName(md.sourceURL), _
String.Format("AlbumArt_{0}_Large.jpg", _
md.getItemInfo("WM/WMCollectionID")))
If IO.File.Exists(ImagePath) Then
Dim ms As New IO.MemoryStream
Dim npImage As Image = New Bitmap(ImagePath)
npImage.Save(ms, ImageFormat.Bmp)
Dim imgBytes() As Byte = ms.ToArray()
Return Convert.ToBase64String(imgBytes)
Else
Return String.Empty
End If
End Function
この複雑なコードは、SOAP や XML を転送方法として使用している場合に、グラフィック ファイルをネットワーク上で簡単に渡せるようにするものです。後でこの記事の中で、クライアント アプリケーション内のイメージを再構築するコードを紹介します。
音楽を再生する
ナビゲーションおよびメディア ライブラリのコードを作成後の残りの作業は、特定のアルバム、アーティスト、または曲を開始する手段を提供することです。PlayItems クラスに PlayItem、PlayAlbum、および PlayArtist という 3 つのメソッドを作成し、1 曲、アルバム全体、または特定のアーティストの曲すべてをそれぞれ再生できるようにしました。
Imports NMCSHost.SharedMediaPlayer
Imports Microsoft.MediaPlayer.Interop
Public Class PlayItems
Inherits MarshalByRefObject
Public Sub PlayItem(ByVal ContentID As String)
Dim md As IWMPMedia
Dim pl As IWMPPlaylist
Dim ImagePath As String
pl = Player.mediaCollection.getByAttribute( _
"WM/WMContentID", ContentID)
If Not pl Is Nothing Then
md = pl.Item(0)
If Not md Is Nothing Then
Player.currentMedia = _
Player.newMedia(md.sourceURL)
Player.Ctlcontrols.play()
End If
End If
End Sub
Public Sub PlayAlbum(ByVal AlbumName As String)
Dim pl As IWMPPlaylist
pl = Player.mediaCollection.getByAlbum(AlbumName)
Player.currentPlaylist = pl
Player.Ctlcontrols.play()
End Sub
Public Sub PlayArtist(ByVal ArtistName As String)
Dim pl As IWMPPlaylist
pl = Player.mediaCollection.getByAuthor(ArtistName)
Player.currentPlaylist = pl
Player.Ctlcontrols.play()
End Sub
End Class
音楽をキューに入れるためのサポートを追加すると便利です。こうすると、再生中の曲を置換せずに現在のミックスに音楽をさらに追加できるようになります。ただし、このサンプルではこの機能は省略しています。
ホストのリモート処理を設定する
HTTP を経由して SOAP を使用し、3 つのクラスをクライアント アプリケーションで使用可能にするには、ホスト システムの起動時に、リモート処理用にこれらのクラスを登録する必要があります。
Protected Overrides Sub OnLoad(ByVal e As System.EventArgs)
SharedMediaPlayer.Player = Me.wmp
SetUpRemoting()
End Sub
Private Sub SetUpRemoting()
RegisterWellKnownServiceType( _
GetType(NavigationalControls), _
"NMCSNavigation.soap", _
WellKnownObjectMode.SingleCall)
RegisterWellKnownServiceType( _
GetType(MediaInformation), _
"NMCSInformation.soap", _
WellKnownObjectMode.SingleCall)
RegisterWellKnownServiceType( _
GetType(PlayItems), _
"NMCSPlayItems.soap", _
WellKnownObjectMode.SingleCall)
'チャネルを登録します
Dim channel As New Channels.Http.HttpChannel(9000)
Channels.ChannelServices.RegisterChannel(channel)
End Sub
ポートはハードコードされています。設定ダイアログや構成ファイルを追加して、この値を変更できるようにすることもできます。メイン フォームにこの追加を行ったところで、今度はクライアントに移ることができます。ホストがエラーなしに実行されることを確認してください。また、Microsoft Visual Studio® .NET の新しいインスタンスを起動してクライアント アプリケーションで作業する前に、ホストを実行した状態にしておいてください。
クライアント アプリケーションを作成する
この記事の冒頭の図 1 に示すように、クライアント アプリケーションは Pocket PC 用にデザインされています。ホスト アプリケーションが Visual Studio .NET の自身のインスタンスで実行された状態で、Visual Studio .NET の 2 番目のインスタンスを開き、Visual Basic .NET の新しい Smart Device プロジェクトを作成しました。このインターフェイスはグラフィックスを追加すれば見栄えがよくなりますが、TabControl のタブでナビゲーション コマンドごとにボタンを作成し、2 番目のタブの TreeView でメディア ライブラリ情報を表示するにとどめました。
Web 参照をリモート処理ホストに追加する
ここでは Web サービスではなくリモート処理を使用していますが、HTTP 転送プロトコルと SOAP 形式を使用しているため、Visual Studio .NET での Web 参照の概念を活用できます。これによって、3 つのクラスが他の参照と同じようにコード内で使用できるようになるため、コーディングがかなり単純になります。参照を追加するには、ホスト アプリケーションが実行されている必要があります。そうしないと、入力するアドレスが見つかりません。ここでは、http://duncanma:9000/NMCSNavigation.soap?wsdl、http://duncanma:9000/NMCSInformation.soap?wsdl、および http://duncanma:9000/NMCSPlayItems.soap?wsdl に参照を追加しましたが、各自の環境に合わせてサーバー名を変更する必要があります。
注: リモート サーバーで「Web 参照の追加」の概念を使用するこの方法は、複雑な型を使用しているときには機能しない場合があります。SOAP メッセージの作成と解析に使用されるコードは、Web サービスとリモート処理とで異なります。この違いによって、一部のリモート サーバーでこの方法が機能しない場合があります。この例では文字列と配列を渡すだけなので、問題はありません。
「再生中」情報のポーリングを行う
ここでは 3 秒間隔のタイマーを使用して、クライアント アプリケーションがホストをポーリングし、現在再生中の項目に行われた変更を検出するように設定しました。項目が変更されている場合には、その内容に関連するイメージを引き出し、Base64 からイメージにデコードし、PictureBox コントロールに表示します。
Private Sub trmUpdate_Tick( _
ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles trmUpdate.Tick
trmUpdate.Enabled = False
UpdateNowPlaying()
trmUpdate.Enabled = True
End Sub
Private Sub UpdateNowPlaying()
Dim currentSong As Information.Song
Dim objResult As Object
Dim info As Information.MediaInformationService
Try
info = New Information.MediaInformationService
objResult = info.CurrentItem
currentSong = CType(objResult, Information.Song)
Me.pbNetworkError.Visible = False
If Not currentSong Is Nothing Then
If Me.lblCurrentSong.Text <> currentSong.Title Then
Me.lblCurrentSong.Text = currentSong.Title
Me.lblCurrentArtist.Text = currentSong.Authors
Dim img As Bitmap
Dim pic As String = _
info.GetImage(currentSong.ContentID)
Dim picBuff() As Byte
picBuff = Convert.FromBase64String(pic)
img = New Bitmap(New IO.MemoryStream(picBuff))
Me.pbAlbum.Image = img
End If
End If
Catch ex As Exception
NetworkError()
End Try
End Sub
クライアント アプリケーションでは、NetworkError() を呼び出してエラーを処理します。この小さな関数は強引なポップアップ メッセージ ボックスの代わりに、フォームに小さなエラー イメージを表示するだけです。
メディア ライブラリ ビュー
フォームの 2 番目のタブでは、ホストのメディア ライブラリのビューを作成し、ツリー ビュー形式でアーティストとアルバムを表示するようにしました。曲の一覧は含めませんでしたが、ツリー内の項目の 1 レベルとして追加することは可能です。

図 3. 音楽を探して選択できる [Media Library] タブ
[Media Library] タブが選択されるたびに、エントリがダウンロードされていないかどうかを確認し、ダウンロードされていなければ、作者の一覧をプルダウンします。これは非常に時間のかかるプロセスで、独自のスレッドに分岐させる方が適していますが、高度な機能は将来のバージョンで対処することにします。
Private Sub remoteTab_SelectedIndexChanged( _
ByVal sender As Object, _
ByVal e As System.EventArgs) _
Handles remoteTab.SelectedIndexChanged
If remoteTab.SelectedIndex = 1 Then
If Me.mediaLibraryTree.Nodes.Count = 0 Then
LoadMediaInfo()
End If
End If
End Sub
Private Sub LoadMediaInfo()
With Me.mediaLibraryTree
.Nodes.Clear()
Dim authors As String()
Dim albums As String()
authors = info.GetAuthors
For Each author As String In authors
'ツリーに追加します
Dim authorNode As TreeNode
authorNode = .Nodes.Add(author)
authorNode.SelectedImageIndex = 1
authorNode.ImageIndex = 1
Next
End With
End Sub
特定のアーティストのアルバムは、アーティストが TreeView で選択されてから、アーティスト ノードの下にノードを追加して読み込みます。
Private Sub mediaLibraryTree_AfterSelect( _
ByVal sender As Object, _
ByVal e As TreeViewEventArgs) _
Handles mediaLibraryTree.AfterSelect
If e.Node.Parent Is Nothing Then
If e.Node.Nodes.Count = 0 Then
Dim albums As String()
albums = info.GetAlbumsByArtist(e.Node.Text)
For Each album As String In albums
If Not album Is Nothing _
AndAlso Not album = String.Empty Then
Dim albumNode As TreeNode
albumNode = e.Node.Nodes.Add(album)
albumNode.SelectedImageIndex = 0
albumNode.ImageIndex = 0
End If
Next
e.Node.Expand()
End If
End If
End Sub
便利なもう 1 つの機能として、この TreeView の再読み込み/更新ボタンを組み込むこともできますが、それが必要になるほどホストのメディア ライブラリが頻繁に変更されることはほとんどないものと判断しました。
音楽を再生するために、[Media Library] の TreeView に、[Play] というメニュー項目を含むコンテキスト メニューを追加しました。コンテキスト メニューを表示し [Play] をクリックしたときに、アーティストが選択されている場合には、ホストでそのアーティストのすべての音楽が再生されます。アルバムが選択されている場合には、そのアルバムが再生されます。
最終仕上げ
この単純で賢いリモート コントロールの構築作業は以上ですが、独自の機能を追加してさらに完全なシステム コントローラを作り上げるアイデアをいろいろお持ちの方もいらっしゃると思います。このサンプルを基にしてコントローラを構築される方は、是非ご連絡ください。みなさんがどのようなものを作成されるか、非常に興味があります。ここで作成したリモート コントロールは独自のパーソナル音楽システムで機能する必要があるため、このサンプルにこれ以上何かを加えることは、必要なバグの修正以外にないと思います。筆者が構築したコントロールをご覧になりたい場合、完成したコードは GotDotNet の MusicXP workspace に掲載されます。MusicXP システムの現在のビルドは、GotDotNet でユーザー サンプルとしてご覧いただけます。
コーディングの課題
Coding4Fun コラムの最後に、興味のある方のためにコーディングの課題を付け加えました。今回の記事の課題は、リモート処理を使用する作品を作成して、楽しいと思えるようなことを達成することです。マネージ コード (Visual Basic .NET、C#、J#、またはマネージ C++) が理想的ですが、COM インターフェイスを使用したアンマネージ コンポーネントでもかまいません。作成したものを GotDotNet に掲載し、電子メール メッセージ (duncanma@microsoft.com) で、何を作成したか、そしてそれがおもしろい理由についてお知らせください。アイデアはいつお送りいただいてもかまいませんが、コード サンプルは、サンプルそのものではなく、リンクを送信してください (あらかじめお礼を申し上げます)。
おもしろいアイデアがありましたら、 duncanma@microsoft.com まで (英語で) お知らせください。ではコーディングをお楽しみください。
Coding4Fun