• 播放全景视频【二】:Unity中使用AVProVideo播放Android上的全景视频


    一、AVProVideo是什么?

    它是一个专注视频播放的Unity插件,可以发布到多个平台。

    该插件做了一些优化,本人用skybox播放vr视频,只有40帧,换成该插件fps升到70帧。

    参考上一篇:
    播放全景视频【一】:用unity Video Player视频播放器来播放360全景视频

    在这里插入图片描述

    二、哪里得到它

    1、下一个吧,【github】:https://github.com/RenderHeads/UnityPlugin-AVProVideo
    2、800美元买一个吧,【Unity assets stores】:https://assetstore.unity.com/packages/tools/video/avpro-video-ultra-edition-184294
    3、从别人的工程里面刨一个吧,或者邻居借一个

    三、案例:从android的共享文件夹里面读取全景视频文件

    一共挂载三个物体,如下图所示(1,2,3):
    在这里插入图片描述

    (一)、MediaPlayer播放器组件设置

    常用设置:
    1、MediaSource,资源的加载方式,【路径加载】还是【引用加载】
    2、Auto Play,自动播放
    3、Loop,循环播放
    4、Muted,静音
    在这里插入图片描述
    在这里插入图片描述

    (二)、为什么要从android内存卡里面直接读取media资源?

    android里面的apk包,有大小限制,具体查看官网。
    当build的apk太大时,如果祖坟葬得好可以跑几个步骤,如果祖坟葬的有点歪,可能启动就黑屏,甚至花屏。所以啊,把video资源从streamingasset或者resources里面剥离出来就显得比较重要了!

    (三)、Unity 如何从android设备上读取video资源?

    app要访问本地的资源,是需要许可,那么在哪里设置呢?

    【Project Settings】/【OtherSettings】/【Configuration】
    [Install Location = Force Internal]
    [Write Permission = Internal]
    
    • 1
    • 2
    • 3

    在这里插入图片描述

    四、播放android设备上的video文件

    (一)videoPlayer的设置

    设置资源从路径加载:
    在这里插入图片描述

    (二)播放存储空间里视频(代码实现)

    • 1、如何获取共享目录下的movies文件夹路径
      在这里插入图片描述
      那么这个目录地址要怎么写呢?——如何获取android内存共享文件夹目录?
      感谢外国友人分享了主要的代码,我自己直接搬过来用了
    
    /// 
    /// Unity获取Android的内存共享文件夹目录
    /// 
    /// 
    
    public static string GetMoviesFolder()
    {
        /*
          Found a way, to get the internal download or movies... folder without an native plugin. Tho its a bit hackerish.
          You need to set:
          BuildSettings->PlayerSettings->Android->OtherSettings->InstallLocation to "ForceInternal"
          Than create an static string in your script:
         */
    
        string[] temp = (Application.persistentDataPath.Replace("Android", "")).Split(new string[] { "//" }, System.StringSplitOptions.None);
    
        return (temp[0] + "/Movies");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 2、如何播放内存【movies】共享文件夹下的视频资源呢
      call method:OpenMedia(), then call Play()
    /// 
    /// 播放存储空间中的mp4视频
    /// 
    /// 视频文件名
    void PlayVideoFromSD(string name)
    { 
        try
        {
            myMediaPlayer.OpenMedia(MediaPathType.AbsolutePathOrURL, $"{GetMoviesFolder()}/{name}", true);
            myMediaPlayer.Rewind(true);
            myMediaPlayer.Play();
        }
        catch (Exception ex)
        {
            Debug.Log($"播放视频出错,请参考出错信息:{ex.Message}");
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    (三)实现循环播放——如何添加一个【播放完毕】的监听事件

    作者已经提供了Events的接口了,如下图所示:
    在这里插入图片描述
    那么怎么侦听【播放结束】的事件呢?作者也提供了,如下图所示:
    在这里插入图片描述
    自己手动添加侦听,一旦检测到【播放结束】,那就切下一首~~~这就是循环播放

    myMediaPlayer.Events.AddListener((MediaPlayer mp, MediaPlayerEvent.EventType eventType, ErrorCode errorCode) =>
    {
        switch (eventType)
        {
            //case MediaPlayerEvent.EventType.MetaDataReady:
            //    break;
            //case MediaPlayerEvent.EventType.ReadyToPlay:
            //    Debug.Log("ReadyToPlay状态");
            //    break;
            //case MediaPlayerEvent.EventType.Started:
            //    Debug.Log("Started状态");
            //    break;
            //case MediaPlayerEvent.EventType.FirstFrameReady:
            //    Debug.Log("第一帧准备完成");
            //    if (displayUGUI != null)
            //        displayUGUI.color = new Color(255, 255, 255);
            //    break;
            case MediaPlayerEvent.EventType.FinishedPlaying:  //视频播放完毕
                Debug.Log("视频播放完毕,即将播放下一条!!!");
                var nextVideo = GetNext(videosList, currentVideo);
                PlayVideoFromSD(nextVideo);
                break;
            default:
                break;
        }
    });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    五、关键代码

    using Pvr_UnitySDKAPI;
    using RenderHeads.Media.AVProVideo;
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Linq;
    using UnityEngine;
    using UnityEngine.UI;
    using Cysharp.Threading.Tasks;
    
    
    /// 
    /// 播放android上的视频资源
    /// 
    public class PlayVideoFromAndroid : MonoBehaviour
    {
        /// 
        /// 视频文件列表
        /// 
        [Header("视频文件列表")]
        [SerializeField]
        public List<string> videosList = new List<string>();
    
        /// 
        /// 挂载视频播放组件的物体
        /// 
        public GameObject videoPlayObj;
    
        /// 
        /// 当前播放的视频
        /// 
        private string currentVideo;
    
        /// 
        /// MediaPlayer对象
        /// 
        private MediaPlayer myMediaPlayer;
    
        private void Awake()
        {
            myMediaPlayer = videoPlayObj.GetComponent<MediaPlayer>();
        }
    
        // Start is called before the first frame update
        void Start()
        {
            #region         ======普通初始化======begin
            PlayFirstVideo();
            #endregion      ======普通初始化======end
    
            #region         ======事件绑定======begin
            myMediaPlayer.Events.AddListener((MediaPlayer mp, MediaPlayerEvent.EventType eventType, ErrorCode errorCode) =>
            {
                switch (eventType)
                {
                    //case MediaPlayerEvent.EventType.MetaDataReady:
                    //    break;
                    //case MediaPlayerEvent.EventType.ReadyToPlay:
                    //    Debug.Log("ReadyToPlay状态");
                    //    break;
                    //case MediaPlayerEvent.EventType.Started:
                    //    Debug.Log("Started状态");
                    //    break;
                    //case MediaPlayerEvent.EventType.FirstFrameReady:
                    //    Debug.Log("第一帧准备完成");
                    //    if (displayUGUI != null)
                    //        displayUGUI.color = new Color(255, 255, 255);
                    //    break;
                    case MediaPlayerEvent.EventType.FinishedPlaying:  //视频播放完毕
                        Debug.Log("视频播放完毕,即将播放下一条!!!");
                        var nextVideo = GetNext(videosList, currentVideo);
                        PlayVideoFromSD(nextVideo);
                        break;
                    default:
                        break;
                }
            });
            #endregion      ======事件绑定======end
        }
    
        private async UniTask PlayFirstVideo()
        {
            await UniTask.DelayFrame(2,cancellationToken:this.GetCancellationTokenOnDestroy());  //延时2帧,等待初始化
            
            myMediaPlayer.AudioMuted = true;
            currentVideo = videosList[0];
            PlayVideoFromSD(currentVideo);
        }
    
        /// 
        /// 播放存储空间中的mp4视频
        /// 
        /// 视频文件名
        void PlayVideoFromSD(string name)
        { 
            try
            {
                myMediaPlayer.OpenMedia(MediaPathType.AbsolutePathOrURL, $"{GetMoviesFolder()}/{name}", true);
                myMediaPlayer.Rewind(true);
                myMediaPlayer.Play();
            }
            catch (Exception ex)
            {
                Debug.Log($"播放视频出错,请参考出错信息:{ex.Message}");
            }
        }
    
        /// 
        /// Unity获取Android的内存共享文件夹目录
        /// 
        /// 
    
        public static string GetMoviesFolder()
        {
            /*
              Found a way, to get the internal download or movies... folder without an native plugin. Tho its a bit hackerish.
              You need to set:
              BuildSettings->PlayerSettings->Android->OtherSettings->InstallLocation to "ForceInternal"
              Than create an static string in your script:
             */
    
            string[] temp = (Application.persistentDataPath.Replace("Android", "")).Split(new string[] { "//" }, System.StringSplitOptions.None);
    
            return (temp[0] + "/Movies");
        }
    
        private void Update()
        {
            //播放下一个视频,just for testing
            //if (Controller.UPvr_GetKeyDown(0, Pvr_KeyCode.TRIGGER) || Controller.UPvr_GetKeyDown(0, Pvr_KeyCode.TRIGGER) || Input.GetKeyDown(KeyCode.Mouse0))
            //{
            //    currentVideo = GetNext(videosList, currentVideo);
            //    PlayVideoFromSD(currentVideo);
            //}
        }
    
        /// 
        /// 在一个列表中,获取【给定元素】的下一个元素,如果【给定元素】是最末尾的元素,【下一个元素】为列表的第一个元素
        /// 
        /// 
        /// 
        /// 
        /// 
        public static T GetNext<T>(List<T> list, T t)
        {
            var subList = list.SkipWhile(x => !x.Equals(t)).Skip(1);//SkipWhile——凡是满足给定条件的item都会被忽略
            return subList.Count() == 0 ? list[0] : subList.First();
        }
    
        private void OnDestroy()
        {
            myMediaPlayer.Stop();
            myMediaPlayer = null;
            myMediaPlayer.Events.RemoveAllListeners();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
  • 相关阅读:
    Vue2高级-全局事件总线、TodoList案例(改良版)
    软件测试 —— 常见的自动化测试架构!
    Java开发者必备:支付宝沙箱环境支付远程调试指南
    vue中使用swiper的时候第二轮事件不会触发
    Mac连接虚拟机(Linux系统)
    docker容器中没有vi编辑器的解决办法
    数组相关的面试OJ题
    只需根据接口文档,就能轻松开发 get 和 post 请求的脚本,你会做吗?
    扬尘在线监测是什么?如何实现?
    计算机视觉面试题整理
  • 原文地址:https://blog.csdn.net/dzj2021/article/details/127818553