赞
踩
挺久没有写博客了,这是2020年的第一篇博客,先说说写这篇博客的原因吧。
去年下半年负责一个新项目,项目里面有需要播放大量视频的需求,由于Demo时间比较紧急就没有下功夫去做这块。
问题1:改变视频进度时进度条出现回滚。
问题2:改变视频进度时出现音画不一致问题,音频较视频有延迟。
问题3:开发周期小,时间紧急,代码不规范。
马上就要春节放假了,赶在了放假之前把关卡都完成了,今天有空就重构了一下视频播放的代码,公司工作嗨不起来,回家一边嗨一边重构代码吧。
Demo代码 https://github.com/wuxiaomu/VideoPlayerDemo
我用的是unity自带的视频播放器组件VideoPlayer,很多人觉得VideoPlayer贼难用,确实!因为unity没有把视频播放器的组件整合起来,只是提供了显示视频的组件,不会用的人当然会说不好用啦,毕竟unity主要是用来做游戏开发的,视频播放的需求没有那么的重要。
Source:代表播放源的来源,有Video Clip,URL两个选项,前者需要直接手动引用视频资源,后者的既可以在栏URL直接填链接地址,也可以在代码中指定,网络链接和本地地址皆可,这里用的就是URL源。
Play On Awake:字面意思,组件激活时就直接播放。
Loop:字面意思,循环播放。
Playback Speed:字面意思,播放速度。
Render Mode:字面意思,渲染模式,这个需要详细介绍一下,包含5个选项,
AspectRatio:屏幕长宽比适应。
AudioOutputMode:音频输出模式,
新建视频控制器脚本“VideoController”,主要是提供了各种控制视频的属性和方法,和UI层分开。
- using System;
- using System.Collections;
- using System.Collections.Generic;
- using UnityEngine;
- using UnityEngine.Events;
- using UnityEngine.Video;
-
- public class VideoController : MonoBehaviour
- {
-
- [SerializeField]
- private bool startAfterPreparation = true;
-
- [Header("Optional")]
-
- [SerializeField]
- private VideoPlayer videoPlayer;
-
- [SerializeField]
- private AudioSource audioSource;
-
- [Header("Events")]
-
- [SerializeField]
- private UnityEvent onPrepared = new UnityEvent();
-
- [SerializeField]
- private UnityEvent onStartedPlaying = new UnityEvent();
-
- [SerializeField]
- private UnityEvent onFinishedPlaying = new UnityEvent();
-
- #region Properties
-
- public bool StartAfterPreparation
- {
- get { return startAfterPreparation; }
- set { startAfterPreparation = value; }
- }
-
- public UnityEvent OnPrepared
- {
- get { return onPrepared; }
- }
-
- public UnityEvent OnStartedPlaying
- {
- get { return onStartedPlaying; }
- }
-
- public UnityEvent OnFinishedPlaying
- {
- get { return onFinishedPlaying; }
- }
-
- public ulong Time
- {
- get { return (ulong)videoPlayer.time; }
- }
-
- public bool IsPlaying
- {
- get { return videoPlayer.isPlaying; }
- }
-
- public bool IsPrepared
- {
- get { return videoPlayer.isPrepared; }
- }
-
- public float NormalizedTime
- {
- get { return (float)(videoPlayer.time / Duration); }
- }
-
- public ulong Duration
- {
- get {
- return videoPlayer.frameCount / (ulong)videoPlayer.frameRate;
- }
- }
-
- public float Volume
- {
- get { return audioSource == null ? videoPlayer.GetDirectAudioVolume(0) : audioSource.volume; }
- set
- {
- if (audioSource == null)
- videoPlayer.SetDirectAudioVolume(0, value);
- else
- audioSource.volume = value;
- }
- }
- #endregion
-
- #region Unity Methods
-
- // Start is called before the first frame update
- void Start()
- {
- if (videoPlayer == null)
- {
- SubscribeToVideoPlayerEvents();
- }
-
- videoPlayer.playOnAwake = false;
- }
-
- // Update is called once per frame
- void Update()
- {
-
- }
-
- private void OnEnable()
- {
- SubscribeToVideoPlayerEvents();
- }
-
- private void OnDisable()
- {
- UnsubscribeFromVideoPlayerEvents();
- }
-
- #endregion
-
- #region Public Methods
-
- public void PrepareForUrl(string url)
- {
- Debug.Log("PrepareForUrl");
- videoPlayer.source = VideoSource.Url;
- videoPlayer.url = url;
- videoPlayer.Prepare();
- }
-
- public void PrepareForClip(VideoClip clip)
- {
- videoPlayer.source = VideoSource.VideoClip;
- videoPlayer.clip = clip;
- videoPlayer.Prepare();
- }
-
- public void Play()
- {
- Debug.Log("Play");
- if (!IsPrepared)
- {
- videoPlayer.Prepare();
- return;
- }
-
- videoPlayer.Play();
- }
-
- public void Pause()
- {
- videoPlayer.Pause();
- }
-
- public void TogglePlayPause()
- {
- if (IsPlaying)
- {
- Pause();
- }
- else
- {
- Play();
- }
- }
-
- public void Seek(float time)
- {
- time = Mathf.Clamp(time, 0, 1);
- XMDebug.Log(time, Duration, time * Duration);
- videoPlayer.time = time * Duration;
- }
- #endregion
-
- #region Private Methods
-
- private void OnPrepareCompleted(VideoPlayer source)
- {
- Debug.Log("OnPrepareCompleted");
- onPrepared.Invoke();
- SetupAudio();
-
- if (StartAfterPreparation)
- Play();
- }
-
- private void OnStarted(VideoPlayer source)
- {
- onStartedPlaying.Invoke();
- }
-
- private void OnFinished(VideoPlayer source)
- {
- onFinishedPlaying.Invoke();
- }
-
- private void OnError(VideoPlayer source, string message)
- {
- Debug.LogError("OnError " + message);
- }
-
- private void SetupAudio()
- {
- Debug.Log("SetupAudio");
-
- if (videoPlayer.audioTrackCount <= 0)
- return;
-
- if (audioSource == null && videoPlayer.canSetDirectAudioVolume)
- {
- videoPlayer.audioOutputMode = VideoAudioOutputMode.Direct;
- }
- else
- {
- videoPlayer.audioOutputMode = VideoAudioOutputMode.AudioSource;
- videoPlayer.SetTargetAudioSource(0, audioSource);
- }
- videoPlayer.controlledAudioTrackCount = 1;
- videoPlayer.EnableAudioTrack(0, true);
- }
-
- private void SubscribeToVideoPlayerEvents()
- {
- if (videoPlayer == null)
- return;
-
- videoPlayer.errorReceived += OnError;
- videoPlayer.prepareCompleted += OnPrepareCompleted;
- videoPlayer.started += OnStarted;
- videoPlayer.loopPointReached += OnFinished;
- }
-
- private void UnsubscribeFromVideoPlayerEvents()
- {
- if (videoPlayer == null)
- return;
-
- videoPlayer.errorReceived -= OnError;
- videoPlayer.prepareCompleted -= OnPrepareCompleted;
- videoPlayer.started -= OnStarted;
- videoPlayer.loopPointReached -= OnFinished;
- }
- #endregion
- }
新建视频播放器界面脚本“UIVideoPanel”。
- using System;
- using System.Collections;
- using System.Collections.Generic;
- using UnityEngine;
- using UnityEngine.Events;
- using UnityEngine.UI;
- using UnityEngine.Video;
-
- [Serializable]
- public class FloatEvent : UnityEvent<float> { }
-
- public class UIVideoPanel : MonoBehaviour
- {
-
- private VideoController controller;
-
- [SerializeField]
- private VideoClip clip;
-
- [SerializeField]
- private GameObject StartPanel;
- [SerializeField]
- private Button PlayBtn;
-
- [SerializeField]
- private Button PlayPauseBtn;
- [SerializeField]
- private Text PlayBtnTxt;
-
- [SerializeField]
- private Slider PositionSlider;
- [SerializeField]
- private Slider PreviewSlider;
-
- [SerializeField]
- private FloatEvent onSeeked = new FloatEvent();
-
- // Start is called before the first frame update
- void Start()
- {
- controller = GetComponent<VideoController>();
- PlayBtn.onClick.AddListener(PlayVideo);
- PlayPauseBtn.onClick.AddListener(ToggleIsPlaying);
- PositionSlider.onValueChanged.AddListener(SliderValueChanged);
-
- StartPanel.SetActive(true);
- }
-
- private void OnDestroy()
- {
- PlayBtn.onClick.RemoveListener(PlayVideo);
- PlayPauseBtn.onClick.RemoveListener(ToggleIsPlaying);
- PositionSlider.onValueChanged.RemoveListener(SliderValueChanged);
- }
-
-
- // Update is called once per frame
- void Update()
- {
- if (controller.IsPlaying)
- {
- PreviewSlider.value = controller.NormalizedTime;
- }
- }
-
- private void PlayVideo()
- {
- StartPanel.SetActive(false);
- controller.PrepareForUrl("C:/Users/4399/AppData/LocalLow/haizileyuan/猫小帅学英语/Android/step/test1/l1t7-2.mp4");
- PlayBtnTxt.text = "Pause";
- }
-
- private void ToggleIsPlaying()
- {
- if (controller.IsPlaying)
- {
- controller.Pause();
- PlayBtnTxt.text = "Play";
- }
- else
- {
- controller.Play();
- PlayBtnTxt.text = "Pause";
- }
- }
-
- private void SliderValueChanged(float value)
- {
- onSeeked.Invoke(value);
- }
- }
注:以上代码是视频播放器demo的代码,非项目代码。
视频播放器需要Video Player和Audio Source,一个播放视频,一个播放声音。通过SetTargetAudioSource设置音频。
videoPlayer.SetTargetAudioSource(0, audioSource);
进度条需要一个用来控制进度(PositionSlider),一个用来显示进度(PreviewSlider)。
切换进度条是利用Seek()方法控制视频的videoPlayer.time 。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。