欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Unity VideoPlayer视频播放器

程序员文章站 2024-03-25 10:01:46
...

挺久没有写博客了,这是2020年的第一篇博客,先说说写这篇博客的原因吧。

去年下半年负责一个新项目,项目里面有需要播放大量视频的需求,由于Demo时间比较紧急就没有下功夫去做这块。

问题1:改变视频进度时进度条出现回滚。

问题2:改变视频进度时出现音画不一致问题,音频较视频有延迟。

问题3:开发周期小,时间紧急,代码不规范。

马上就要春节放假了,赶在了放假之前把关卡都完成了,今天有空就重构了一下视频播放的代码,公司工作嗨不起来,回家一边嗨一边重构代码吧。

Demo代码  https://github.com/wuxiaomu/VideoPlayerDemo

我用的是unity自带的视频播放器组件VideoPlayer,很多人觉得VideoPlayer贼难用,确实!因为unity没有把视频播放器的组件整合起来,只是提供了显示视频的组件,不会用的人当然会说不好用啦,毕竟unity主要是用来做游戏开发的,视频播放的需求没有那么的重要。

Unity VideoPlayer视频播放器
猫小帅的视频播放功能

 

VideoPlayer介绍

Unity VideoPlayer视频播放器

Source:代表播放源的来源,有Video Clip,URL两个选项,前者需要直接手动引用视频资源,后者的既可以在栏URL直接填链接地址,也可以在代码中指定,网络链接和本地地址皆可,这里用的就是URL源。
Play On Awake:字面意思,组件**时就直接播放。
Loop:字面意思,循环播放。
Playback Speed:字面意思,播放速度。
Render Mode:字面意思,渲染模式,这个需要详细介绍一下,包含5个选项,

  • CameraFarPlane(基于摄像机的渲染,渲染在摄像机的远平面上,需要设置用于渲染的摄像机,同时可以修改alpha通道的值做透明效果,可用于背景播放器,我用这种渲染模式)
  • CameraNearPlane(基于摄像机的渲染,渲染在摄像机的*面上,需要设置用于渲染的摄像机,同时可以修改alpha通道的值做透明效果,可用作前景播放器)
  • RenderTexture(渲染在RenderTexture上,可以用来做基于UGUI的播放器)
  • MaterialOverride(将视频画面复制给所选Render的Material。需要选择具有Render组件的物体,可以选择赋值的材质属性。可制作360全景视频和VR视频)
  • APIOnly

AspectRatio:屏幕长宽比适应。
AudioOutputMode:音频输出模式,

  • None:不播放声音
  • AudioSource:用AudioSource播放使用
  • ControlledTracks:控制音轨,填需要的数量,再把对应AudioSource节点拖上去引用

 

VideoController视频控制器

新建视频控制器脚本“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视频播放器界面

新建视频播放器界面脚本“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 。

Unity VideoPlayer视频播放器

Unity VideoPlayer视频播放器