仮想と物理とエトセトラ

xRや物理とかごった煮の備忘録的技術ブログ

HoloLens2のファイルIOを確認する (その2 テキスト、画像、動画読み込み)

今回は前回の続きです。

xr-physics-work-etc.hatenablog.com

今回は、テキスト、画像、動画の読み込みを3種の方法で読み込んでみます。
読み込み方法は以下の通りです。

  • テキスト:WUPのFileIO.ReadTextAsyncFileStorageオブジェクトから読み込み
  • 画像:ファイルパスからSystem.IO.File.ReadAllBytesを用いたByte列読み込み
  • 動画:VideoPlayerコンポーネントでURL(ファイルパス)指定での読み込み

読み込み対象は、前回読み込めなかったMediaServerDevicesRemovableDevicesを除いた14種としました。

1. 準備

準備として読み込み用のコンポーネントとGameObjectを用意します。
読み込み用コンポーネントでは、プルダウンメニューから読み込み対象を選択し、対象ディレクトリのテキスト、画像、動画を読み込み対象とする構成です。

  • StorageReadCheck.cs
クリックで展開
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Video;
using TMPro;

#if WINDOWS_UWP
using System;
using Windows.Storage;
#endif

public class StorageReadCheck : MonoBehaviour
{
    /// <summary>
    /// ストレージ情報クラス
    /// </summary>
    [SerializeField]
    private StorageFolderInfo storageFolderInfo;
    
    /// <summary>
    /// タイトル
    /// </summary>
    [SerializeField]
    private TextMeshPro title;

    /// <summary>
    /// ビデオプレイヤー
    /// </summary>
    [SerializeField]
    private VideoPlayer videoPlayer;

    /// <summary>
    /// 画像表示用MeshRenderer
    /// </summary>
    [SerializeField]
    private MeshRenderer imageMeshRenderer;

    /// <summary>
    /// 文章読み込み用テキスト
    /// </summary>
    [SerializeField]
    private TextMeshPro text;



    // Start is called before the first frame update
    void Start()
    {
        LoadFiles();
    }

    // Update is called once per frame
    void Update()
    {

    }

    private void LoadFiles()
    {
        title.text = storageFolderInfo.targetStorageType.ToString();
        LoadVideo();
        LoadImage();
        LoadText();
    }

    private void LoadVideo()
    {
#if WINDOWS_UWP
        videoPlayer.url = storageFolderInfo.GetStorageFile("test.mp4").Path;
#endif
    }

    private void LoadImage()
    {
        Texture2D texture = new Texture2D(2, 2);
#if WINDOWS_UWP
        texture.LoadImage(System.IO.File.ReadAllBytes(storageFolderInfo.GetStorageFile("test.png").Path));
#endif
        imageMeshRenderer.material.mainTexture = texture;
    }

    private void LoadText()
    {
#if WINDOWS_UWP
        text.text = FileIO.ReadTextAsync(storageFolderInfo.GetStorageFile("test.txt")).GetAwaiter().GetResult();
#endif
    }
}

/// <summary>
/// ストレージ情報クラス
/// </summary>
[System.Serializable]
public class StorageFolderInfo
{
    
    public TargetStorageType targetStorageType;
#if WINDOWS_UWP
    public Dictionary<TargetStorageType, StorageFolder> targetStorageDict = new Dictionary<TargetStorageType, StorageFolder>();
#endif

    /// <summary>
    /// コンストラクタ
    /// ディクショナリ設定
    /// </summary>
    public StorageFolderInfo()
    {
#if WINDOWS_UWP
        targetStorageDict[TargetStorageType.AppCaptures] = KnownFolders.AppCaptures;
        targetStorageDict[TargetStorageType.CameraRoll] = KnownFolders.CameraRoll;
        targetStorageDict[TargetStorageType.DocumentsLibrary] = KnownFolders.DocumentsLibrary;
        targetStorageDict[TargetStorageType.MediaServerDevices] = KnownFolders.MediaServerDevices;
        targetStorageDict[TargetStorageType.MusicLibrary] = KnownFolders.MusicLibrary;
        targetStorageDict[TargetStorageType.Objects3D] = KnownFolders.Objects3D;
        targetStorageDict[TargetStorageType.PicturesLibrary] = KnownFolders.PicturesLibrary;
        targetStorageDict[TargetStorageType.Playlists] = KnownFolders.Playlists;
        targetStorageDict[TargetStorageType.RecordedCalls] = KnownFolders.RecordedCalls;
        targetStorageDict[TargetStorageType.RemovableDevices] = KnownFolders.RemovableDevices;
        targetStorageDict[TargetStorageType.SavedPictures] = KnownFolders.SavedPictures;
        targetStorageDict[TargetStorageType.VideosLibrary] = KnownFolders.VideosLibrary;
        targetStorageDict[TargetStorageType.LocalFolder] = ApplicationData.Current.LocalFolder;
        targetStorageDict[TargetStorageType.LocalCacheFolder] = ApplicationData.Current.LocalCacheFolder;
        targetStorageDict[TargetStorageType.RoamingFolder] = ApplicationData.Current.RoamingFolder;
        targetStorageDict[TargetStorageType.TemporaryFolder] = ApplicationData.Current.TemporaryFolder;
#endif
    }
#if WINDOWS_UWP
    /// <summary>
    /// フォルダ情報取得
    /// </summary>
    /// <returns></returns>
    public StorageFolder GetStorageFolder()
    {
        return targetStorageDict[targetStorageType];
    }
    /// <summary>
    /// ファイル情報取得
    /// </summary>
    /// <param name="fileName"></param>
    /// <returns></returns>
    public StorageFile GetStorageFile(string fileName)
    {
        return targetStorageDict[targetStorageType].GetFileAsync(fileName).GetAwaiter().GetResult();
    }
#endif
}

/// <summary>
/// 対象ストレージタイプ
/// </summary>
public enum TargetStorageType
{
    AppCaptures,

    CameraRoll,

    DocumentsLibrary,

    MediaServerDevices,

    MusicLibrary,

    Objects3D,

    PicturesLibrary,

    Playlists,

    RecordedCalls,

    RemovableDevices,

    SavedPictures,

    VideosLibrary,

    LocalFolder,

    LocalCacheFolder,

    RoamingFolder,

    TemporaryFolder,
}

次に、表示するためのオブジェクトを用意します。
タイトルとテキスト読み込み用にTextMeshPro, 動画再生、画像表示用にQuadを空のGameObjectの配下に作成します。
f:id:napo909:20210829145253p:plain

Quadのマテリアルには、アプリ実行中にテクスチャを更新できるようにAssigned at Runtimeにチェックを入れておきます。
f:id:napo909:20210829153952p:plain

動画再生用のQuadにはVidoPlayerコンポーネントをアタッチします。
f:id:napo909:20210829150047p:plain

先ほど作成したStorageReadCheckコンポーネントを親オブジェクトにアタッチして読み込み対象をプルダウンメニューから設定します。
f:id:napo909:20210829145743p:plain

当該オブジェクト(今回の場合FileLoadTest)をコピーし、それぞれ読み込みたい対象の読み込み対象をプルダウンメニューを変更して設定します。
最後に作成したFileLoadTestを空オブジェクトの配下に変更し、GridObjectCollectionコンポーネントで整列させます。
f:id:napo909:20210829150306p:plain f:id:napo909:20210829150322p:plain

2. 動作確認

ディレクトリにtest.png, test.mp4, test.txtを配置してアプリを起動すると読み込み結果が確認できます。
※作成対象のディレクトリの対応は前回を対象xr-physics-work-etc.hatenablog.com

実際に読み込みを行った結果は以下の通りです。
テキスト、画像はどこからでも読み込みができ、パス指定でUnity標準コンポーネントに読み込ませている動画のみ、アプリ用ディレクトリ中のフォルダからのみ読み込みができる結果となりました。

StorageFolder テキスト (UWP ReadTextAsync) 画像 (バイト列) 動画(ファイルパス読み込み)
KnownFolders.AppCaptures ○       ○       ×      
KnownFolders.CameraRoll ○       ○       ×      
KnownFolders.DocumentsLibrary ○       ○       ×      
KnownFolders.MusicLibrary ○       ○       ×      
KnownFolders.Objects3D ○       ○       ×      
KnownFolders.PicturesLibrary ○       ○       ×      
KnownFolders.Playlists ○       ○       ×      
KnownFolders.RecordedCalls ○       ○       ×      
KnownFolders.SavedPictures ○       ○       ×      
KnownFolders.VideosLibrary ○       ○       ×      
ApplicationData.Current.LocalFolder ○       ○       ○      
ApplicationData.Current.LocalCacheFolder ○       ○       ○      
ApplicationData.Current.RoamingFolder ○       ○       ○      
ApplicationData.Current.TemporaryFolder ○       ○       ○      

f:id:napo909:20210829150620j:plain

動画がアプリ固有ディレクトリ以外で読み込めない理由を確認してみます。
アプリ固有ディレクトリ中のTempState/UnityPlayer.logを確認すると対象ディレクトリ中のファイル読み込み時に以下のメッセージが出力されていました。
WindowsMediaFoundation received empty file U:\USERS\[ユーザ名]\[対象ディレクトリ]\test.mp4

Unity標準のVideoPlayerの場合はファイルパス読み込みのため、UWPの一般フォルダの厳しい読み込み権限の問題で、ファイルの内容が読み込めないのかもしれません。
今のところ、動画読み込みにVideoPlayerを用いる場合はアプリ固有のディレクトリに動画ファイルを置くしかありませんが、バイト列で動画を読み込めるようなアセットがあれば、他のテキストや画像と同じようにKnownFoldersからも読み込めるかもしれません。