なんとなく

誰得感満載な記事が多いかも。Mono関係とLinuxのサーバ関係、レビューとか。

今いる区画の音楽を流してみる(AndroidでLibOpenMetavereを使って)

はじめに

にゃんぱすー(手

セカンドライフ 技術系 Advent Calendar 2013 : ATNDの2個目のエントリです。1つ目のXamarin.AndroidでNativeライブラリ(libopenmetaverseの中のlibopenjpeg-dotnet)を使ってみた - なんとなくは、クロスポストした関係でセカンドライフの技術系というよりもXamarin.Androidでの話の色が濃くなってしまったかと考えています。ですので、今回はセカンドライフの技術的な話にしたいと思います。

また、去年は完走できなかったので、今年は完走させたいですね。ただ、いろいろな人に記事書いてほしいなと思うところもあります。
個人的には、2013年の技術的な出来事として公式Viewerにおける日本語IME関連に関する修正についてのまとめみたいな記事を誰かに書いてほしいと期待しているのですが、なかなかうまくは行きませんね。

さて、今回は、TextViewerだとしても、やっぱり機能としてほしいなというところの区画の音楽の再生について書いていきたいと思います。
具体的には、Xamarin.AndroidでLibopenmetaverseを使用し、自アバターがいるParcel*1に設定されているStreamingのURIを取得し、再生してみます。

Parcelに設定してあるStreamingのURIの取得し、再生する

libopenmetaverseで現在アバターがいる区画に設定されているStreamingのURI取得する方法

libopenmetaverseを使用して、自アバターがいるParcelに設定されているStreamingのURIを取得するには、ログインした状態で

Client.Parcels.RequestParcelProperties

メソッドを使用します。

使い方は、
OpenMetaverse_ParcelManager_RequestParcelProperties
を見ると
コードについては

public void RequestParcelProperties(
	Simulator simulator,
	int localID,
	int sequenceID
)

引数については

simulator (Simulator)
Simulator containing the parcel
localID (Int32)
Simulator-local ID of the parcel
sequenceID (Int32)
An arbitrary integer that will be returned with the ParcelProperties reply, useful for distinguishing between multiple simultaneous requests

とあります。
simulatorについては、現在いるSIMは、
LibOpenMetaverseを利用してSIMのアバターリストと位置を得る - なんとなく
でも扱っているように以下のメソッドで取得できます。

Client.Network.CurrentSim

ただ、localIDについてはそのParcelを指定しなければなりません。
そこで、

Client.Parcels.GetParcelLocalID

メソッドを使用して取得します。

使い方は
OpenMetaverse_ParcelManager_GetParcelLocalID
を見ると
コードについては

public int GetParcelLocalID(
	Simulator simulator,
	Vector3 position
)

引数については

simulator (Simulator)
Simulator parcel is in
position (Vector3)
Vector3 position in simulator (Z not used)

とあります。

上述したように現在いるSIMについては、Client.Network.CurrentSimで取得します。
positionについては、現在いるSIMのポジションを

Client.Self.SimPosition

メソッドを使用して取得します。

まとめると以下になります。

Client.Parcels.RequestParcelProperties (Client.Network.CurrentSim, Client.Parcels.GetParcelLocalID(Client.Network.CurrentSim,Client.Self.SimPosition), 0);

そして、サーバより得られる情報は

Client.Parcels.ParcelProperties

というイベントで取得できます。

Xamarin.AndoidでAndroid.Media.MediaplayerでStreamingを流す方法

手順としては

  • usingディレクティブにAndroid.Mediaを定義
  • MediaPlayerで再生、停止の処理を指定

と至って簡単です。

using Android.Media;

usingディレクティブにAndroid.Mediaを定義します。

再生については、まず準備として
Android.Media.MediaPlayerで

SetAudioStreamType(Stream.Music);
SetDataSource(Uri.EscapeUriString(Mediaurl));

とAudioStreamTypeをStream.Musicにして、DataSourceにエスケープしたstreamingのURIを設定します。
そしてPrepareAsync()として非同期に準備します。

再生する際は、準備出来たらStart()してあげればいいようです。
停止については、Stop()して、Reset()してあげればいいようです。
なお、Reset()を忘れるとエラーが出ました。

まとめると以下のようになります。(PlayButton,StopButtonというButtonを使用しています)

~略
using Android.Media;
~略			
MediaPlayer Mp = new MediaPlayer ();
string Mediaurl = "streamingのURI"

//Playbuttonの処理
PlayButton.Click += (object sender, EventArgs e) => {
	if (Mp.IsPlaying || Mediaurl == string.Empty) {
		return;
	}

	Mp.SetAudioStreamType(Stream.Music);
	Mp.SetDataSource(Uri.EscapeUriString(Mediaurl));
	Mp.PrepareAsync();
};

//stopbuttonの処理
StopButton.Click += (object sender, EventArgs e) => {
	if (Mp.IsPlaying) {
		Mp.Stop ();
		Mp.Reset ();
	}
};

Mp.Prepared += (object sender, EventArgs e) =>{
	Mp.Start();
};
~略
Parcelに設定してあるStreamingのURIの取得し、Xamarin.Androidで再生する

上述した

  • libopenmetaverseで現在アバターがいる区画に設定されているStreamingのURI取得する方法
  • Xamarin.AndoidでAndroid.Media.MediaplayerでStreamingを流す方法

をまとめると以下のようになります(TextStatusというTextViewを用意してステータスを表示するようにしています。)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Android.App;
using Android.Content;
using Android.OS;
using Android.Runtime;
using Android.Views;
using Android.Widget;

using Android.Media;

using OpenMetaverse;

namespace dvgtuViewer
{
	[Activity (Label = "ParcelMedia")]			
	public class ParcelMedia : Activity
	{
		//ログインデータ渡し
		DvgtuInstance dvgtuInstance = DvgtuInstance.GlobalInstance;
		private GridClient Client{ get { return dvgtuInstance.Client; } }

		//mediaplayer関連
		MediaPlayer Mp;

		private const string PREPARING = "Preparing";
		private const string NOWPLAYING = "Now Playing";
		private const string STOPPED = "Stopped";

		protected override void OnCreate (Bundle bundle)
		{
			base.OnCreate (bundle);

			SetContentView (Resource.Layout.ParcelMedia);

			//UI
			Button PlayButton = FindViewById<Button> (Resource.Id.playbutton);
			Button StopButton = FindViewById<Button> (Resource.Id.stopbutton);
			TextView TextStatus = FindViewById<TextView> (Resource.Id.textStatus);

			string Mediaurl = string.Empty;
			Mp = new MediaPlayer ();
			RunOnUiThread(() => TextStatus.Text = STOPPED);


			//今いる区画の情報を取得
			Client.Parcels.RequestParcelProperties (Client.Network.CurrentSim, Client.Parcels.GetParcelLocalID(Client.Network.CurrentSim,Client.Self.SimPosition), 0);

			//event
			//区画の情報の中からstreamのURLを得る
			Client.Parcels.ParcelProperties += (object sender, ParcelPropertiesEventArgs e) => {
				if (e.Result != ParcelResult.Single) {
					return;
				}
				Mediaurl = e.Parcel.MusicURL;
			};

			//Playbuttonの処理
			PlayButton.Click += (object sender, EventArgs e) => {
				if (Mp.IsPlaying || Mediaurl == string.Empty) {
					return;
				}

				RunOnUiThread(() => TextStatus.Text = PREPARING);
				Mp.SetAudioStreamType(Stream.Music);
				Mp.SetDataSource(Uri.EscapeUriString(Mediaurl));
				Mp.PrepareAsync();
			};

			//stopbuttonの処理
			StopButton.Click += (object sender, EventArgs e) => {
				if (Mp.IsPlaying) {
					Mp.Stop ();
					Mp.Reset ();
					RunOnUiThread(() => TextStatus.Text = STOPPED);
				}
			};

			Mp.Prepared += (object sender, EventArgs e) =>{
				Mp.Start();
				RunOnUiThread(() => TextStatus.Text = NOWPLAYING);
			};
		}

		protected override void OnDestroy ()
		{
			Mp.Dispose ();
			base.OnDestroy ();
		}		
	}
}
なんだよ中華タブレット

実はメインでデバッグに使用している中華タブレットで上記のコードでは再生できず、他のエミュレータやSony Tablet Pでは再生されるという現象が発生しました。カスタムファームを使っているからかもしれません。
ただ、
Ti.Media.createAudioPlayer / unknown file format error » Community Questions & Answers » Appcelerator Developer Center
でも同様現象が起こっているようです。
記載されているshoutcastでの回避方法を取るとshoutcastのみなら再生出来ました。

一体何なんだろう。

まとめ

libopenmetaverseで現在アバターがいる区画に設定されているStreamingのURI取得するのは1行のメソッドとそれを受け取るイベントハンドラを書けば取得できるという簡単なもの。取得したURIAndroid.Media.Mediaplayerに渡せば、すでにあるもので再生してくれる。実に素晴らしい。shoucastでの曲のタイトルとかmetadataを取って表示させたいところだったのだけれども、Android.Media.MediaplayerにまかせてしまうとMediaplayerからは取得できなかった。他の方法では取得できそうなのだけれども長くなりそうだったので今回は割愛しました。

でも、でもさ、冷静に考えるとlibopenmetaverseをXamarin.Androidに移植して使っているのって私だけじゃない?この紹介は意味あんのかな?誰得感満載。。。
いや、Viewerの中ではこうやって処理されているんだよっていうのが誰かしらに伝わればいいんだよ!(自分に言い聞かせています)

2013年の前半は体調が優れず開発が全く進まなかったけど、ちょっと安定してきたので実装するべきものをピックアップしてなんとか2014年のAdventCalendarではAndroid版のdvgtuViewerで良い知らせを書ければいいなぁ。*2

*1:Secondlifeにおいて、simulator内の4mx4mが最小の区画単位で、SIMの所有者とは別に所有者を設定でき、その区画の権限や音楽などを設定することができる。

*2:そんなに実装に時間かかるのか?