unity5.6.1 videoPlayer
unity5.6开始增加了videoPlayer,使得视频播放相对比较简单,项目需求进行了一下研究应用,也遇到很多坑,Google 百度一下发现确实有这些问题,一些简单问题如下:
1)播放无声音
2)通过slider控制播放进度
3)视频截图(texture->texture2d)
4)视频结束时事件激活
基本上无太大问题,以上四个问题解决方案在下文红色文字区域,先介绍一下video Player应用,后续对这四个问题进行解决(代码之后有更新)。
(一)新建video Player 可以在ui下田间video Play组建,也可以直接右键-video-videoplayer,添加后可以看到如下图所示的组件
本文主要重点说一下一下参数:source有两种模式clip模式和url模式,clip则可以直接通过videoClip进行播放,url则可以通过url进行播放。renderMode为渲染模式,既可以为camera,material等,如果是使用ui播放的选用render texture,本文采用此模式。audioOutputMode有三种,none模式,direct模式(没尝试)和audiosource模式,本文采用audiosource模式,采用此模式时只需要将audiosource组建拖入上图中videoPlayer中的audiosource参数槽中即可,不需要任何处理,但有时会出现拖入后videoPlayer中的audiosource参数槽消失,且无声音播放,所以一般采用代码添加,如下所示:
//代码添加 videoPlayer = gameObject.AddComponent<VideoPlayer>(); //videoPlayer = gameObject.GetComponent<VideoPlayer>(); audioSource = gameObject.AddComponent<AudioSource>(); //audioSource = gameObject.GetComponent<AudioSource>(); videoPlayer.playOnAwake = false; audioSource.playOnAwake = false; audioSource.Pause();
(二)视频播放的控制与音频/动画播放类似,videoPlayer有play/pause等方法,具体可以参见后面完整代码。
在调用视频播放结束时事件loopPointReached(此处为借鉴别人称呼,此事件其实并不是视频播放结束时的事件),顾名思义,此事件为达到视频播放循环点时的事件,即当videoplay 的isLooping属性为true(即循环播放视频)时,视频结束时调用此方法,所以当视频非循环播放时,此事件在视频结束时调用不到。要想调用此方法可以把视频设置为循环播放,在loopPointReached指定的事件中停止播放视频
(三)关于视频播放的ui选择问题,选用render texture时需要指定target texture。
1)在project面板上create-renderTexture,并把新建的renderTexture拖到videoplayer相应的参数槽上
2)在Hierarchy面板上新建ui-RawImage,并把上一步新建的renderTexture拖到RawImage的texture上即可。
其实可以不用这么处理,videoPlayer有texture变量,直接在update里面把texture值赋给RawImage的texture即可,代码如下
rawImage.texture = videoPlayer.texture;
视频截图时可以通过videoPlayer.texture,把图像保存下来不过需要把texture转变为texture2d,虽然后者继承在前者,但是无法强制转货回去,转换以及存储图片代码如下:
private void SaveRenderTextureToPNG(Texture inputTex, string file) { RenderTexture temp = RenderTexture.GetTemporary(inputTex.width, inputTex.height, 0, RenderTextureFormat.ARGB32); Graphics.Blit(inputTex, temp); Texture2D tex2D = GetRTPixels(temp); RenderTexture.ReleaseTemporary(temp); File.WriteAllBytes(file, tex2D.EncodeToPNG()); } private Texture2D GetRTPixels(RenderTexture rt) { RenderTexture currentActiveRT = RenderTexture.active; RenderTexture.active = rt; Texture2D tex = new Texture2D(rt.width, rt.height); tex.ReadPixels(new Rect(0, 0, tex.width, tex.height), 0, 0); RenderTexture.active = currentActiveRT; return tex; }
最后说一下通过slider控制视频播放进度的问题,
通过slider控制视频播放存在两个问题,一方面在update实时把videoPlayer.time 赋值给slider,一方面需要把slider的value反馈给time,如果用slider的OnValueChanged(float value) 方法则存在矛盾,导致问题。所以可以通过UI事件的BeginDrag和EndDrag事件
事件进行,即当BeginDrag时,停止给slider赋值,当EndDrag时重新开始赋值。如下图所示
全代码
using System; using System.Collections; using System.Collections.Generic; using System.IO; using UnityEngine; using UnityEngine.UI; using UnityEngine.Video; public class VideoController : MonoBehaviour { public GameObject screen; public Text videoLength; public Text currentLength; public Slider volumeSlider; public Slider videoSlider; private string video1Url; private string video2Url; private VideoPlayer videoPlayer; private AudioSource audioSource; private RawImage videoScreen; private float lastCountTime = 0; private float totalPlayTime = 0; private float totalVideoLength = 0; private bool b_firstVideo = true; private bool b_adjustVideo = false; private bool b_skip = false; private bool b_capture = false; private string imageDir =@"D:\test\Test\bwadmRe"; // Use this for initialization void Start () { videoScreen = screen.GetComponent<RawImage>(); string dir = Path.Combine(Application.streamingAssetsPath,"Test"); video1Url = Path.Combine(dir, "01.mp4"); video2Url = Path.Combine(dir, "02.mp4"); //代码添加 videoPlayer = gameObject.AddComponent<VideoPlayer>(); //videoPlayer = gameObject.GetComponent<VideoPlayer>(); audioSource = gameObject.AddComponent<AudioSource>(); //audioSource = gameObject.GetComponent<AudioSource>(); videoPlayer.playOnAwake = false; audioSource.playOnAwake = false; audioSource.Pause(); videoPlayer.audioOutputMode = VideoAudioOutputMode.AudioSource; videoPlayer.SetTargetAudioSource(0, audioSource); VideoInfoInit(video1Url); videoPlayer.loopPointReached += OnFinish; } #region private method private void VideoInfoInit(string url) { videoPlayer.source = VideoSource.Url; videoPlayer.url = url; videoPlayer.prepareCompleted += OnPrepared; videoPlayer.isLooping = true; videoPlayer.Prepare(); } private void OnPrepared(VideoPlayer player) { player.Play(); totalVideoLength = videoPlayer.frameCount / videoPlayer.frameRate; videoSlider.maxValue = totalVideoLength; videoLength.text = FloatToTime(totalVideoLength); lastCountTime = 0; totalPlayTime = 0; } private string FloatToTime(float time) { int hour = (int)time / 3600; int min = (int)(time - hour * 3600) / 60; int sec = (int)(int)(time - hour * 3600) % 60; string text = string.Format("{0:D2}:{1:D2}:{2:D2}", hour, min, sec); return text; } private IEnumerator PlayTime(int count) { for(int i=0;i<count;i++) { yield return null; } videoSlider.value = (float)videoPlayer.time; //videoSlider.value = videoSlider.maxValue * (time / totalVideoLength); } private void OnFinish(VideoPlayer player) { Debug.Log("finished"); } private void SaveRenderTextureToPNG(Texture inputTex, string file) { RenderTexture temp = RenderTexture.GetTemporary(inputTex.width, inputTex.height, 0, RenderTextureFormat.ARGB32); Graphics.Blit(inputTex, temp); Texture2D tex2D = GetRTPixels(temp); RenderTexture.ReleaseTemporary(temp); File.WriteAllBytes(file, tex2D.EncodeToPNG()); } private Texture2D GetRTPixels(RenderTexture rt) { RenderTexture currentActiveRT = RenderTexture.active; RenderTexture.active = rt; Texture2D tex = new Texture2D(rt.width, rt.height); tex.ReadPixels(new Rect(0, 0, tex.width, tex.height), 0, 0); RenderTexture.active = currentActiveRT; return tex; } #endregion #region public method //开始 public void OnStart() { videoPlayer.Play(); } //暂停 public void OnPause() { videoPlayer.Pause(); } //下一个 public void OnNext() { string nextUrl = b_firstVideo ? video2Url : video1Url; b_firstVideo = !b_firstVideo; videoSlider.value = 0; VideoInfoInit(nextUrl); } //音量控制 public void OnVolumeChanged(float value) { audioSource.volume = value; } //视频控制 public void OnVideoChanged(float value) { //videoPlayer.time = value; //print(value); //print(value); } public void OnPointerDown() { b_adjustVideo = true; b_skip = true; videoPlayer.Pause(); //OnVideoChanged(); //print("down"); } public void OnPointerUp() { videoPlayer.time = videoSlider.value; videoPlayer.Play(); b_adjustVideo = false; //print("up"); } public void OnCapture() { b_capture = true; } #endregion // Update is called once per frame void Update () { if (videoPlayer.isPlaying) { videoScreen.texture = videoPlayer.texture; float time = (float)videoPlayer.time; currentLength.text = FloatToTime(time); if(b_capture) { string name = DateTime.Now.Minute.ToString() + "_" + DateTime.Now.Second.ToString() + ".png"; SaveRenderTextureToPNG(videoPlayer.texture,Path.Combine(imageDir,name)); b_capture = false; } if(!b_adjustVideo) { totalPlayTime += Time.deltaTime; if (!b_skip) { videoSlider.value = (float)videoPlayer.time; lastCountTime = totalPlayTime; } if (totalPlayTime - lastCountTime >= 0.8f) { b_skip = false; } } //StartCoroutine(PlayTime(15)); } } }
如果使用插件AVPro Video所有问题不是问题
//-----------------------------------------2018_11_29-----------------------------------------------//
版本为2018.2.14,在新版本中videoplayer已经不存在上述问题。
通过下述两个事件可以添加时间时间和帧时间,但是帧事件要设置sendFrameReadyEvents=true
public event TimeEventHandler clockResyncOccurred; public event FrameReadyEventHandler frameReady;
代码更新
using System.Collections; using System.Collections.Generic; using UnityEngine.UI; using UnityEngine.Video; using UnityEngine; using UnityEngine.EventSystems; /// <summary> /// 视频播放控制脚本 /// </summary> public class CustomerVideoPlayer : MonoBehaviour { public RawImage rawImage; public Button btnPlay; public Button btnPause; public Button btnFornt; public Button btnBack; public Button btnNext; public Slider sliderVolume; public Slider sliderVideo; public Text txtVolume; public Text txtProgress; public Text txtCurrentProgress; //需要添加播放器的物体 public GameObject obj; //是否拿到视频总时长 public bool isShow; //前进后退的大小 public float numBer = 20f; //时 分的转换 private int hour, min; private float time; private float time_Count; private float time_Current; private bool isVideo; private VideoPlayer vPlayer; private AudioSource source; private string urlNetWork = @"D:\001.mp4"; void Start() { //一定要动态添加这两个组件,要不然会没声音 vPlayer = obj.AddComponent<VideoPlayer>(); source = obj.AddComponent<AudioSource>(); vPlayer.playOnAwake = false; source.playOnAwake = false; source.Pause(); btnPlay.onClick.AddListener(delegate { OnClick(0); }); btnPause.onClick.AddListener(delegate { OnClick(1); }); btnFornt.onClick.AddListener(delegate { OnClick(2); }); btnBack.onClick.AddListener(delegate { OnClick(3); }); btnNext.onClick.AddListener(delegate { OnClick(4); }); sliderVolume.value = source.volume; txtVolume.text = string.Format("{0:0}%", source.volume * 100); Init(urlNetWork); } /// <summary> /// 初始化信息 /// </summary> /// <param name="url"></param> private void Init(string url) { isVideo = true; isShow = true; time_Count = 0; time_Current = 0; sliderVideo.value = 0; //设置为URL模式 vPlayer.source = VideoSource.Url; vPlayer.url = url; vPlayer.audioOutputMode = VideoAudioOutputMode.AudioSource; vPlayer.SetTargetAudioSource(0, source); vPlayer.prepareCompleted += OnPrepared; vPlayer.Prepare(); } /// <summary> /// 声音设置 /// </summary> /// <param name="value"></param> public void AdjustVolume(float value) { source.volume = value; txtVolume.text = string.Format("{0:0}%", value * 100); } /// <summary> /// 调整进度 /// </summary> /// <param name="value"></param> public void AdjustVideoProgress(float value) { if (vPlayer.isPrepared) { vPlayer.time = (long)value; time = (float)vPlayer.time; hour = (int)time / 60; min = (int)time % 60; txtProgress.text = string.Format("{0:D2}:{1:D2}", hour.ToString(), min.ToString()); } } /// <summary> /// 视频控制 /// </summary> /// <param name="num"></param> private void OnClick(int num) { switch (num) { case 0: vPlayer.Play(); Time.timeScale = 1; break; case 1: vPlayer.Pause(); Time.timeScale = 0; break; case 2: sliderVideo.value = sliderVideo.value + numBer; break; case 3: sliderVideo.value = sliderVideo.value - numBer; break; case 4: vPlayer.Stop(); Init(urlNetWork); break; default: break; } } void OnPrepared(VideoPlayer player) { player.Play(); sliderVideo.onValueChanged.AddListener(delegate { AdjustVideoProgress(sliderVideo.value); }); } void Update() { if (vPlayer.isPlaying && isShow) { rawImage.texture = vPlayer.texture; //帧数/帧速率=总时长 如果是本地直接赋值的视频,我们可以通过VideoClip.length获取总时长 sliderVideo.maxValue = vPlayer.frameCount / vPlayer.frameRate; time = sliderVideo.maxValue; hour = (int)time / 60; min = (int)time % 60; txtCurrentProgress.text = string.Format("/ {0:D2}:{1:D2}", hour.ToString(), min.ToString()); //sliderVideo.onValueChanged.AddListener(delegate { ChangeVideo(sliderVideo.value); }); isShow = !isShow; } if (Mathf.Abs((int)vPlayer.time - (int)sliderVideo.maxValue) == 0) { vPlayer.frame = (long)vPlayer.frameCount; vPlayer.Stop(); isVideo = false; return; } else if(isVideo && vPlayer.isPlaying) { time_Count += Time.deltaTime; if ((time_Count - time_Current) >= 1) { sliderVideo.value += 1; time_Current = time_Count; } } } }