なんとなく

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

libopenmetaverseでプロフィールを表示してみる

はじめに

セカンドライフ技術系 Advent Calendar 2014 - Adventarの2個目のエントリです。 libopenmetaverseでフレンドリストを表示してみる - なんとなくからの続きです。 前回、フレンドリストの作成をしました。今回はそのフレンドリストを使って、プロフィールを表示するということについて見て行きたいと思います。

概要

フレンドリストの対象項目(青で囲んだところ)を押下したら、ダイアログで対象のアバターのプロフィールを表示させるということを説明していきたいと思います。今回はプロフィールの表示です。

f:id:takeshich:20141222161653p:plain

f:id:takeshich:20141222161346p:plain

プロフィールダイアログの作成

使用するlibopenmetaverseのメンバーの説明とか

libopenmetaverseにおいて、アバターのプロフィールを取得するには、OpenMetaverse.AvatarManager.RequestAvatarProperties(UUID)メソッドを使用します。

public void RequestAvatarProperties(
    UUID avatarid
)

http://lib.openmetaverse.org/docs/trunk/?topic=html/M_OpenMetaverse_AvatarManager_RequestAvatarProperties.htm

OpenMetaverse.AvatarManager.AvatarPropertiesReplyイベントでAvatarProperties構造体に格納されたデータを取得できます。

http://lib.openmetaverse.org/docs/trunk/?topic=html/E_OpenMetaverse_AvatarManager_AvatarPropertiesReply.htm

AvatarProperties構造体は

Member Description
AboutText
AllowPublish Should this profile be published on the web
BornOn
CharterMember
FirstLifeImage First Life image ID
FirstLifeText First Life about text
Flags Flags of the profile
FromOSD(OSD)
GetOSD()
Identified
MaturePublish Is this a mature profile
Online Avatar Online Status
Partner
ProfileImage Profile image ID
ProfileURL Web URL for this profile
Transacted

と定義されています。

http://lib.openmetaverse.org/docs/trunk/?topic=html/T_OpenMetaverse_Avatar_AvatarProperties.htm

また、グループや興味などのデータも同様に OpenMetaverse.AvatarManager.AvatarGroupsReplyイベント OpenMetaverse.AvatarManager.AvatarInterestsReplyイベント において値を取得することができます。

http://lib.openmetaverse.org/docs/trunk/?topic=html/E_OpenMetaverse_AvatarManager_AvatarGroupsReply.htm

http://lib.openmetaverse.org/docs/trunk/?topic=html/E_OpenMetaverse_AvatarManager_AvatarInterestsReply.htm

画像を取得することについては、 LibOpenMetaverseとCSJ2KでSIMのMinimap画像を表示してみる - なんとなく においても触れているように OpenMetaverse.AssetManager.RequestImage()メソッドを使用します。 AvatarProperties構造体のProfileImageを使用して、プロフィール画像を取得できます。

http://lib.openmetaverse.org/docs/trunk/?topic=html/Overload_OpenMetaverse_AssetManager_RequestImage.htm

http://lib.openmetaverse.org/docs/trunk/?topic=html/T_OpenMetaverse_TextureDownloadCallback.htm

実装

省きながらではあるけど、実際にどういうふうに実装しているかについて見て行きたいと思います。ソースコードとその説明です。

ソース

OnCreateDialogをそのまま

public override Dialog OnCreateDialog (Bundle savedInstanceState)
{
    var builder = new AlertDialog.Builder (Activity);

    LayoutInflater layout = (LayoutInflater)Activity.GetSystemService (Context.LayoutInflaterService);
    View view = layout.Inflate (Resource.Layout.profileDialogContent, null);

    AvatarsPropertiesDone = new AutoResetEvent (false);
    //profileデータの取得
    Client.Avatars.RequestAvatarProperties (AvatarID);
    AvatarsPropertiesDone.WaitOne ();

    //Partnerのデータ取得
    if (AvatarProfileContent.PartnerID.Count != 0) {
        DisplayNamesDone = new AutoResetEvent (false);
        Client.Avatars.GetDisplayNames (AvatarProfileContent.PartnerID, PartnerDisplayNamesCallback);
        DisplayNamesDone.WaitOne ();
        Client.Avatars.RequestAvatarNames (PartnerBadIDs);

    }

    //avatarImageの取得
    if (AvatarProfileContent.ProfileImageID != UUID.Zero) {
        BeginDownloadingImage (AvatarProfileContent, view.FindViewById<ImageView> (Resource.Id.imageView1), view.FindViewById<ProgressBar> (Resource.Id.progressBar1), Activity);
    } else
    {
        view.FindViewById<ImageView> (Resource.Id.imageView1).SetImageBitmap (BitmapFactory.DecodeResource (res, Resource.Drawable.Icon));
        view.FindViewById<ImageView> (Resource.Id.imageView1).Visibility = ViewStates.Visible;
        view.FindViewById<ProgressBar> (Resource.Id.progressBar1).Visibility = ViewStates.Gone;
    }

    view.FindViewById<TextView> (Resource.Id.textViewBorndate).Text = AvatarProfileContent.BornOn;
    if (string.IsNullOrEmpty (AvatarProfileContent.PartnerName)) 
    {
        view.FindViewById<TextView> (Resource.Id.textViewPartner).Text = "None";
    } else 
    {
        view.FindViewById<TextView> (Resource.Id.textViewPartner).Text = AvatarProfileContent.PartnerName;
    }
    view.FindViewById<TextView> (Resource.Id.textViewDescription).Text = AvatarProfileContent.AboutText;
    view.FindViewById<TextView> (Resource.Id.textViewURI).Text = AvatarProfileContent.ProfileURL;

    builder.SetView (view);
    if (ShownAvatarData.IsDefaultDisplayName)
    {
        builder.SetTitle (ShownAvatarData.AvatarDisplayName);
    } else {
        builder.SetTitle (ShownAvatarData.AvatarName);
    }

    builder.SetPositiveButton ("閉じる", (sender, args) => {
        this.Dismiss();
    });

    return builder.Create();
}

イベントとかコールバックとか

Client.Avatars.AvatarPropertiesReply += (object sender, AvatarPropertiesReplyEventArgs e) => {

    List<UUID> partnerID = new List<UUID>();
    partnerID.Add(e.Properties.Partner);
    AvatarProfileContent = new AvatarProfile(ShownAvatarData.AvatarID,ShownAvatarData.AvatarName,ShownAvatarData.AvatarDisplayName,
        e.Properties.AboutText,e.Properties.BornOn,e.Properties.ProfileURL,e.Properties.ProfileImage,partnerID);
    AvatarsPropertiesDone.Set();

};

Client.Avatars.UUIDNameReply += (object sender, UUIDNameReplyEventArgs e) => {
    foreach (KeyValuePair<UUID,string> kvp in e.Names) {
        PartnerIDName = new AvatarData(kvp.Key,kvp.Value,null,false);
    }
};

DisplayNamesCallback += new AvatarManager.DisplayNamesCallback (PartnerDisplayNamesCallback);
TextureDownloadCallback += new TextureDownloadCallback(ProfileTextureDownloadCallback);

コールバック実態

private static void PartnerDisplayNamesCallback(bool success,AgentDisplayName[] DisplayNames,UUID[] BadIDs)
{

    foreach(AgentDisplayName displayname in DisplayNames)
    {
        PartnerIDName = new AvatarData (displayname.ID, displayname.LegacyFullName, displayname.DisplayName,displayname.IsDefaultDisplayName);
    }

    foreach (UUID s in BadIDs) {
        PartnerBadIDs.Add(s);
    }
    DisplayNamesDone.Set ();
}

private static void ProfileTextureDownloadCallback(TextureRequestState state, AssetTexture assetTexture)
{
    ManagedImage mImage;

    if (state == TextureRequestState.Finished)
    {
        OpenJPEG.DecodeToImage (assetTexture.AssetData, out mImage, out ProfileImage);
        textureDone.Set();
    }   
}

非同期で画像取得とか

private static async void BeginDownloadingImage (AvatarProfile avatarProfile, ImageView imageView,ProgressBar progressBar,Activity context)
{
    Bitmap imageBitmap = null;

    //progress
    context.RunOnUiThread (() =>imageView.SetImageBitmap (BitmapFactory.DecodeResource(res,Resource.Drawable.Icon)));

    imageBitmap = await DownloadingProfileImageAsync (avatarProfile);

    context.RunOnUiThread (() =>{
        imageView.SetImageBitmap (imageBitmap);

        //ぐるぐる回っているのをやめて、取得したBitmapを表示させる
        imageView.Visibility = ViewStates.Visible;
        progressBar.Visibility = ViewStates.Gone;

    });
        
}

private static Bitmap DownloadingProfileImage(AvatarProfile avatarProfileContent)
{
    Bitmap imageBitmap = null;
    AutoResetEvent downloadDone = new AutoResetEvent (false);
    var task1 = Task.Factory.StartNew (() => {

        textureDone = new AutoResetEvent (false);
        Client.Assets.RequestImage (avatarProfileContent.ProfileImageID, ImageType.Baked, ProfileTextureDownloadCallback);
        textureDone.WaitOne ();
        imageBitmap = GetCroppedBitmap (Bitmap.CreateScaledBitmap (ProfileImage, 128, 128, false));
        downloadDone.Set();

    });
    downloadDone.WaitOne ();
    return imageBitmap;
}

private static Task<Bitmap> DownloadingProfileImageAsync(AvatarProfile avatarProfile)
{
    return Task.Run(() => DownloadingProfileImage(avatarProfile));
}
実装説明
  • フレンドリストの項目を押下した際にDialogFragmentを作成し、同時にAvatarのUUIDをDialogFragmentに渡します。
  • RequestAvatarProperties メソッドを使用して、AvatarPropertiesを取得します。
  • 取得したAvatarProperties.ProfileImageより、非同期でプロフィール画像をRequestImageメソッドを使用して取得します。
  • 取得したAvatarPropertiesより、BornOn,AboutText,Partner,URLを表示します。
  • 取得できたプロフィール画像を表示します。

f:id:takeshich:20141222223430p:plain

f:id:takeshich:20141222223436p:plain

フレンドリストからプロフィールダイアログへの値渡しのソースは、Xamarin.Android関連なので省いてしまいました。値渡しについてはParcelableインターフェイスを実装したクラスを使って、DialogFragment作成前にインスタンスに保存して、作成後に取り出しています。

まとめ

libopenmetaverseに関して、結構説明したような気がします。でも、実は重要な部分であるチャットとか3Dについては説明していないです。おそらく、来年のアドベントカレンダーでできるかもしれません。

来年は個人的にはtextviewerをリリースしたいです。でも、同じことを去年の記事でも書いてました。体調ややる気と相談になり個人的要因がとても強いです。

textviewerがリリースできれば、移植しているlibopenmetaverseのAndroid版について本家にmergeを相談したいと思ってます。Xamarin.iOSでもlibopenmetaverseは動いているようですので、現行はデスクトップ向けですが、ポータブルな環境でも使えるライブラリとして利用できるようにしたいなぁという妄想もあります。