使ってみよう! Live Framework

第9回Mesh-Enabled Web アプリケーション ―― 共有メディアプレイヤー

共有メディアプレイヤー

これまでも少し紹介したように、Live Frameworkによりデータだけでなくアプリケーション自体を共有することが可能です。今回は、アプリケーションの共有を意識したMesh-enabled Webアプリケーションの簡単なサンプルを作ります。

今回作成するアプリケーションを図1に示します。フォルダ内のメディアファイルをボタンとして表示し、クリックするとそのファイルを再生するメディアプレイヤーです。停止ボタンもない見た目も中身も大雑把な作りですが、大きなひとつの特徴は、このアプリケーションをユーザー間で共有したり複数デバイス上で同時実行したときに、アプリケーションに対する操作(クリックで再生)が同期して行われるという点です。

図1 共有メディアプレイヤー
図1 共有メディアプレイヤー

複数ユーザーで実行した場合、全員が同じメディアファイルを再生することになるわけですね。アプリケーション間の通信には、これまで本連載で紹介したData FeedとData Entryリソースを使います。アプリケーション作成後にアプリケーションの共有方法についても触れています。

作成するメディアプレイヤーが参照するファイルはLive Meshフォルダ内の拡張子がwmaまたはwmvのファイルを参照するものとします。

前回に紹介したとおり、Mesh-enabled Webアプリケーションは許可なくMesh上のLive Meshフォルダにはアクセスできませんので、事前にアクセス許可をアプリケーションに設定します。メディアプレイヤーを共有するユーザーも同様にフォルダアクセス許可の設定とフォルダ自身の共有も必要になります。Live Meshフォルダではなくアプリケーション自身のデータとしてメディアファイルを保存することも可能ですが、今回はこのような仕様にしています。

メディアプレイヤーの作成

まずはメディアプレイヤーのUIやフォルダ参照部分を作成し、その後に同期処理部分を作ります。

それではSilverlight Mesh-enabled Web アプリケーションプロジェクトを作成しましょう。プロジェクトの作成と実行方法は第7回で紹介した通りです。そちらを参照してください。今回もVB.NETを使用しています。

フォルダとメディアファイルの準備

メディアプレイヤーの作成にあたり、メディアファイルをLive Meshフォルダに用意しておきましょう。

Live Framework Developer Sandboxにサインインして、Live Desktop上で適当なフォルダを作成しメディアファイルをアップロードします図2⁠。ここではMediaというフォルダ名にしました。

図2 フォルダ作成とメディアファイルのアップロード
図2 フォルダ作成とメディアファイルのアップロード

ちなみにこれらのメディアファイルはLive Desktop上で再生が可能になっています。

ファイル一覧の表示

Live Meshフォルダ内にあるファイルを取得し、アプリケーションのウィンドウに一覧表示する部分を作成します。SilverlightプロジェクトにあるPage.xamlのGrid以下を次のように編集します。

<Grid x:Name="LayoutRoot" Background="White">
    <MediaElement x:Name="Player" Visibility="Collapsed" />
    <ItemsControl x:Name="MediaListControl" Margin="5" >
        <ItemsControl.ItemsPanel>
            <ItemsPanelTemplate>
                <StackPanel />
            </ItemsPanelTemplate>
        </ItemsControl.ItemsPanel>
        <ItemsControl.ItemTemplate>
            <DataTemplate>                    
                <Button Margin="1" Click="Button_Click">
                    <Button.Content>
                        <StackPanel Orientation="Horizontal">
                            <Image Source="sound.png" Width="16" Height="16" VerticalAlignment="Center" Margin="2" />
                            <TextBlock Text="{Binding Title}" VerticalAlignment="Center" />
                        </StackPanel>
                    </Button.Content>
                </Button>
            </DataTemplate>
        </ItemsControl.ItemTemplate>
    </ItemsControl>
</Grid>

メディアファイルの再生のためのMediaElementコントロール(非表示にしています。つまり動画も音声のみ再生です)とコレクションを表示するためにItemsControlコントロールを記述しています。ItemsControlに設定するコレクションのアイテムはData Entryのリソースを格納するようこの後コーディングします。

コレクションの各アイテムの表示方法は<DataTemplate>に定義しています。Buttonコントロール内にImage、TextBlockコントロールがあります。TextBlockにはリソースのTitleプロパティを表示するよう指定しています。Imageコントロールには音符の画像を指定しています。画像は適当に用意していただくか、このコントロール自体を削除しても構いません。

続いてMesh上のLive Meshフォルダからメディアファイルを取得し、アプリケーションに表示するロジック部分を記述します。ファイルPage.xaml.vb内に次のメソッドを追加します。

' メディアファイル一覧表示
Private Sub ShowMediaList()

    ' ItemsControl アイテムクリア
    MediaListControl.Items.Clear()

    For Each mo In From m In meshApp.LiveOperatingEnvironment.Mesh.MeshObjects.Entries _
                   Where m.Resource.Type = "LiveMeshFolder"
        ' Live Mesh フォルダ一覧

        Try
            For Each df In From d In mo.DataFeeds.Entries _
                           Where d.Resource.Type = "LiveMeshFiles"
                ' Live Mesh ファイル一覧

                ' メディアファイルのみ取得
                Dim medias = From de In df.DataEntries.Entries _
                             Where de.Resource.Type = "File" AndAlso _
                                  (de.Resource.Title.ToUpper.EndsWith(".WMA") OrElse _
                                   de.Resource.Title.ToUpper.EndsWith(".WMV"))

                For Each m In medias
                    ' ItemsControl に追加
                    MediaListControl.Items.Add(m.Resource)
                Next
            Next
        Catch ex As InvalidOperationException
            ' Ignore
        End Try
    Next
End Sub

上記メソッドではMesh上のMesh ObjectからLive Meshフォルダであるものを探し、さらにその中のファイルについて拡張子がwmaとwmvであるものをItemsControlのItemsコレクションへ追加しています。コレクションへ追加しているアイテムはData EntryのResourceプロパティの内容(DataEntryResourceオブジェクト)です。

このメソッドをアプリケーションロード時に呼び出すようmeshAppLoadedメソッド内に追記しておきましょう。

Private Sub meshAppLoaded(ByVal o As Object, ByVal e As EventArgs)
    'Mesh application service object is now loaded and usable.
    ShowMediaList()
End Sub

ボタンクリック時のイベント処理は、クリック時に対象のメディアファイルが再生されるよう仮に次のように記述しておきます。

Private Sub Button_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs)
    Dim button = DirectCast(sender, Button)
    Dim resource = DirectCast(button.DataContext, DataEntryResource)
    Player.Source = resource.EditMediaLink
    Player.Play()
End Sub

引数のsenderオブジェクトからクリックされたボタンを取得し、ButtonオブジェクトのDataContextプロパティからData Entryのリソースを取得しています。メディアファイルへのURLはEditMediaLinkプロパティから取得し、これをMediaElementコントロール(名前Player)のSourceプロパティに指定することでメディアファイルの再生が可能です。最後にPlayメソッドを呼んでいます。

Live Meshフォルダのアクセス許可

ここまでを実行してみましょう。初回実行時のみ、Azure Services Developer Portal上にプロジェクトの作成や、アプリケーションパッケージのアップロード作業などが必要です。

初めて実行する場合は、Live Meshフォルダへのアクセスをアプリケーションに対して許可していないため、ひとつもファイルが表示されないはずです。Live Framework Developer Sandboxにサインインし、Appタブから対象アプリケーションを選択しアクセス許可を変更します。⁠edit」リンクをクリックし、移動ページ先の「change」リンクからメディアファイルが入っているフォルダを選択します図3、4⁠。

図3 アプリケーションページ
図3 アプリケーションページ
図4 アクセス許可の設定
図4 アクセス許可の設定

アクセス許可設定後、再度アプリケーションを実行してみましょう。フォルダ内のメディアファイルがボタンとして表示され、クリックするとそのファイルが再生されれば成功です。

プレイヤー操作の同期

作成したメディアプレイヤーは、アプリケーションを共有しているユーザーが同時に使用していたとしても独立して動くだけです。アプリケーションを共有している場合、その操作が同期するように改良してみましょう。

操作の同期には、アプリケーションのデータ保存領域として利用できるData FeedとData Entryリソースを使用します。以下のクラスを用意し、メディアプレイヤーに対する操作をひとつのオブジェクトとして表します。

Public Class PlayerCommand
    Private _command As String = 
    Public Property Command() As String
        Get
            Return _command
        End Get
        Set(ByVal value As String)
            _command = value
        End Set
    End Property

    Private _argument As String = 
    Public Property Argument() As String
        Get
            Return _argument
        End Get
        Set(ByVal value As String)
            _argument = value
        End Set
    End Property
End Class

Commandプロパティに「play」などの操作を表す文字列、ArgumentプロパティにはメディアファイルのURLなどの値を設定して使います。本記事では再生を表す「play」コマンドしか使用しませんが、拡張した場合にも利用できるようにとこのようにしています。このクラスのひとつのオブジェクトを、ひとつのData Entryの値としてMesh上に保存します。

Data EntryはData Feed内に格納する必要があります。アプリケーション自身のData FeedコレクションにひとつのData Feedを用意し、ここにData Entryを追加します。

メディアファイルの再生などの処理は、Data Feed内のData Entryに対する変更通知を受けたっときに行います。Data Feed内には複数のData Entryが存在する可能性があるため、最新のData Entryを参照するものとします。Data FeedやData Entryなどの変更通知の受信については第5回でも紹介しています。

ここまでのメディアプレイヤー動作イメージを図5に示します。

図5 操作の同期イメージ
図5 操作の同期イメージ
複数のユーザーが、アプリケーションを共有し、アプリケーションのインスタンスを実行している場合……
  1. ユーザーの操作によりアプリケーションがData Entryを追加します。
  2. Data Entryの追加はすべてのアプリケーションに通知されます。
  3. 通知を受信したアプリケーションは最新のData Entryを参照します。
  4. 最後にプレイヤーに対する操作を実行します。

それではコードを書いていきましょう。まず、Data Feedの追加です。アプリケーション ロード時にData Feedがひとつもない場合は新しく作成し追加します。meshAppLoadedメソッド内を次のように変更します。

Private Sub meshAppLoaded(ByVal o As Object, ByVal e As EventArgs)
    'Mesh application service object is now loaded and usable.

    If meshApp.DataFeeds.Entries.Count = 0 Then
        ' Data Feed がない場合

        ' Data Feed 追加完了時のイベント関連付け
        AddHandler meshApp.DataFeeds.AddCompleted, AddressOf DataFeed_AddCompleted

        ' Data Feed の追加
        Dim feed = New DataFeed("commands")
        meshApp.DataFeeds.AddAsync(feed, Nothing)
    Else
        ' その他イベント関連付け
        AddHandlers()
    End If

    ' ファイル一覧表示
    ShowMediaList()
End Sub

追加処理は非同期のため追加完了時のイベントの関連付けを行っています。また、Data Feedの追加完了後、または既にData Feedがあった場合はData Feedがあることを確認した後に、イベントの関連付けの処理を行います。それぞれ必要なメソッドの内容は次の通りです。

' Data Feed 追加完了
Private Sub DataFeed_AddCompleted(ByVal sender As Object, ByVal e As System.ComponentModel.AsyncCompletedEventArgs)
    AddHandlers()
    RemoveHandler meshApp.DataFeeds.AddCompleted, AddressOf DataFeed_AddCompleted
End Sub

' イベント関連付け
Private Sub AddHandlers()
    ' Data Feed 内の Data Entry の変更通知受信
    AddHandler meshApp.DataFeeds.Entries.First.DataEntries.ChangeNotificationReceived, _
        AddressOf DataEntries_ChangeNotificationReceived

    ' Data Feed を最新に更新完了
    AddHandler meshApp.DataFeeds.Entries.First.UpdateCompleted, _
       AddressOf DataFeed_UpdateCompleted

    ' Data Feed 内にData Entry を追加完了 
    AddHandler meshApp.DataFeeds.Entries.First.DataEntries.AddCompleted, _
        AddressOf DataEntry_AddCompleted
End Sub

イベント関連付けは以下の3種類を処理しています。

  • Data Feed内のData Entryの変更通知を受信したときのイベント
  • Data Feedを最新に更新したときのイベント
  • Data Feed内にData Entryを追加完了したときのイベント

Data Entry変更通知受信イベントは、メディアプレイヤーの操作を行うタイミングでしたね。最新のData Entryを取得するためData Feedの更新を行い、その更新完了を知るためにData Feed更新完了イベントも処理しています。

Data Entryの追加処理は非同期で行われますが、現在のライブラリではData Entryに対する追加・削除等の処理完了までその他のData Entryも操作ができないという嫌らしい仕様があります。Data Entryの追加が同時実行されないようData Entry追加完了のイベントも処理しています。

イベント処理の前にボタンクリック時の処理を変更します。クリック時に直接メディアファイルを再生していましたが、ここではPlayerCommandオブジェクトを作成しData Entryの値として設定します。そしてData Feedへ追加のみ行うよう変更します。

Private Sub Button_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs)
    Dim button = DirectCast(sender, Button)
    Dim resource = DirectCast(button.DataContext, DataEntryResource)

    'Player.Source = resource.EditMediaLink
    'Player.Play()

    ' Data Entry 作成
    Dim entry = New DataEntry
    entry.Resource.SetUserData(Of PlayerCommand)( _
        New PlayerCommand With { _
            .Command = "play", _
            .Argument = resource.EditMediaLink.ToString})

    Try
        Monitor.Enter(SyncObject)
        ' Data Entry 追加
        meshApp.DataFeeds.Entries.First.DataEntries.AddAsync(entry, Nothing)
    Catch ex As Exception
        Monitor.Exit(SyncObject)
    End Try

End Sub

Data Entry追加時、追加処理が同時実行されないようにしています。追加完了時のイベント処理は次のようになります。

' Data Entry 追加完了
Private Sub DataEntry_AddCompleted(ByVal sender As Object, ByVal e As System.ComponentModel.AsyncCompletedEventArgs)
    Monitor.Exit(SyncObject)
End Sub

Data Entryが変更通知を受けとったとき、Data Feedの更新を行い、Data Feed更新完了後にData Entryを参照し、メディアプレイヤーの操作を処理します。

' Data Entry 変更通知受信
Private Sub DataEntries_ChangeNotificationReceived(ByVal sender As Object, ByVal e As EventArgs)
    ' Data Feed の更新
    meshApp.DataFeeds.Entries.First.UpdateAsync(Nothing)
End Sub

' Data Feed 更新完了
Private Sub DataFeed_UpdateCompleted(ByVal sender As Object, ByVal e As System.ComponentModel.AsyncCompletedEventArgs)
    ' 最新の Data Entry の取得
    Dim entries = From entry In meshApp.DataFeeds.Entries.First.DataEntries.Entries _
          Order By entry.Resource.PublishDate Descending
    If entries.Count = 0 Then
        Exit Sub
    End If

    ' PlayerCommand オブジェクト取得
    Dim command = entries.First.Resource.GetUserData(Of PlayerCommand)()

    ' メディアプレイヤーの操作実行
    Select Case command.Command
        Case "play"
            Player.Source = New Uri(command.Argument)
            Player.Play()
        Case Else
            ' Do nothing
    End Select
End Sub

コーディングは以上です。ここまでを実行してみましょう。ボタンをクリックするとメディアファイルが再生されたでしょうか? 見た目からわかる動作は変化ありませんが、内部ではData Entryの追加とその変更通知を経てメディアファイルが再生されています。メディアプレイヤーを別のWebブラウザ等で開くと操作が同期されていることもわかります。

サンプルでは、Data Entryを追加するばかりで削除はしていません。実際にアプリケーションとして洗練させるには、より多くのData FeedやData Entryの管理が必要になります。

アプリケーションの共有

最後にアプリケーションの共有について紹介します。アプリケーションを共有するにはいくつかの方法があります。Mesh-enabled Webアプリケーションの場合、Mesh barからユーザーを招待する方法が最も簡単です。共有したいアプリケーションのMesh barのMemberから「Invite」をクリックし、招待したいユーザーのメールアドレスを入力します図6⁠。

図6 メンバーの招待
図6 メンバーの招待

招待されたユーザーは図7のようなメールを受け取ります[1]⁠。メール内のリンクからLive Framework Developer Sandboxへ移動し、アプリケーションのインストールが可能になります図8⁠。

図7 招待メール
図7 招待メール
図8 アプリケーションのインストール
図8 アプリケーションのインストール

招待されたユーザーもLive Framework CTPの利用登録が完了している必要があります。

Mesh bar以外からの方法として、Azure Services Developer PortalのMesh-enabled Webアプリケーションのプロジェクト ページにある「Install URL」のアドレス図9をユーザーへ教え、アクセスしてもらうことでもアプリケーションをインストールできます。

図9 Install URL
図9 Install URL

アプリケーション開発時にはプロジェクトをデバッグ実行すると、最新のアプリケーションがLive Desktop上で表示されていたと思いますが、招待された別ユーザーが同じアプリケーションを起動すると古いバージョンのアプリケーションが表示されてしまうかもしれません。そのような場合には、もう一度Azure Services Developer Portalからアプリケーションパッケージをアップロードしてください図9のUpload Packageボタンから⁠⁠。

コードによる招待

Mesh-enabled Webアプリケーションの場合、以上で事足りると思いますが、本連載の第6回で作成したような.NET Frameworkによるクライアントアプリケーションの場合も簡単に補足しておきます。

Live Framework .NET KitやSilverlight Kitを使用している場合、Mesh Objectを共有するためにユーザーに招待メールを送信するメソッドが用意されています。.NET Kitの場合のコードを以下に示します。Silverlight Kitの場合も基本的に同じです。

' Invitation オブジェクトの作成
Dim invitation = New Invitation
Dim expiration = DateTimeOffset.UtcNow.AddDays(7) ' 招待の有効期限
invitation.Expires = expiration

' Member オブジェクトの作成
Dim newMember = New Member("member@example.jp", RoleType.Full, False)
newMember.Resource.PendingInvitation = invitation

' メンバーの追加(招待) 
meshObject.Members.Add(newMember)

招待はInvitationクラスとMemberクラスを使用します。Resource.PendingInvitationプロパティにInvitationオブジェクトを指定したMemberオブジェクトをMesh ObjectのMemberコレクションに追加すると招待メールが送信されます。

Invitationオブジェクトでは有効期限を設定し、Memberオブジェクトはメールアドレスのほか、アクセス許可の種類(コンストラクタ第2引数)とMesh Objectのオーナーか否か(第3引数)を指定可能です。


今回はここまでです。いかがでしたでしょうか。アプリケーションの共有は、今のところLive Framework CTPの利用が招待制のため手間が多いですが、自由に利用できるようになればアイデア次第でおもしろいアプリケーションが作れそうですね。

おすすめ記事

記事・ニュース一覧