Silverlight をインストールするには、ここをクリックします*
Japan変更|すべてのMicrosoft のサイト|サインイン
MSDN
|MSDN ライブラリ|デベロッパー センター|ダウンロード情報|開発ツール製品|コミュニティ|ご意見・ご要望|サイトマップ
MSDN Home > 連載コラム > Coding4Fun (旧シリーズ) > お気楽 .NET

お気楽 .NET

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

Duncan Mackenzie は、日中は MSDN の Microsoft Visual Basic .NET コンテンツ戦略担当として従事し、夜間はコーディングに熱中しています。アール グレー ティーを飲まないことには何をするにも頭が回らないそうですが、それが本当かどうか確かめなくてもよいことを願いましょう。彼の詳細については、彼自身のサイトをご覧ください。


Microsoft