1065 lines
30 KiB
C#
1065 lines
30 KiB
C#
|
|
||
|
// Copyright (C) 2016 Google Inc. All Rights Reserved.
|
||
|
//
|
||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||
|
// you may not use this file except in compliance with the License.
|
||
|
// You may obtain a copy of the License at
|
||
|
//
|
||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||
|
//
|
||
|
// Unless required by applicable law or agreed to in writing, software
|
||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
|
// See the License for the specific language governing permissions and
|
||
|
// limitations under the License.
|
||
|
|
||
|
using UnityEngine;
|
||
|
using UnityEngine.UI;
|
||
|
using System.Collections;
|
||
|
using System.Runtime.InteropServices;
|
||
|
using System;
|
||
|
using System.Collections.Generic;
|
||
|
|
||
|
/// <summary>
|
||
|
/// Plays video using Exoplayer rendering it on the main texture.
|
||
|
/// </summary>
|
||
|
public class GvrVideoPlayerTexture : MonoBehaviour {
|
||
|
|
||
|
private const int MIN_BUFFER_SIZE = 3;
|
||
|
private const int MAX_BUFFER_SIZE = 15;
|
||
|
|
||
|
/// <summary>
|
||
|
/// The video texture array used as a circular buffer to get the video image.
|
||
|
/// </summary>
|
||
|
private Texture2D[] videoTextures;
|
||
|
private int currentTexture;
|
||
|
|
||
|
/// <summary>
|
||
|
/// The video player pointer used to uniquely identify the player instance.
|
||
|
/// </summary>
|
||
|
private IntPtr videoPlayerPtr;
|
||
|
|
||
|
/// <summary>
|
||
|
/// The video player event base.
|
||
|
/// </summary>
|
||
|
/// <remarks>This is added to the event id when issues events to
|
||
|
/// the plugin.
|
||
|
/// </remarks>
|
||
|
private int videoPlayerEventBase;
|
||
|
|
||
|
private Texture initialTexture;
|
||
|
|
||
|
private bool initialized;
|
||
|
private int texWidth = 1024;
|
||
|
private int texHeight = 1024;
|
||
|
private long lastBufferedPosition;
|
||
|
private float framecount = 0;
|
||
|
|
||
|
private Graphic graphicComponent;
|
||
|
private Renderer rendererComponent;
|
||
|
|
||
|
/// <summary>
|
||
|
/// The render event function.
|
||
|
/// </summary>
|
||
|
private IntPtr renderEventFunction;
|
||
|
|
||
|
private bool processingRunning;
|
||
|
private bool exitProcessing;
|
||
|
private bool playOnResume;
|
||
|
|
||
|
/// <summary>List of callbacks to invoke when the video is ready.</summary>
|
||
|
private List<Action<int>> onEventCallbacks;
|
||
|
|
||
|
/// <summary>List of callbacks to invoke on exception.</summary>
|
||
|
/// <remarks>The first parameter is the type of exception,
|
||
|
/// the second is the message.
|
||
|
/// </remarks>
|
||
|
private List<Action<string, string>> onExceptionCallbacks;
|
||
|
|
||
|
private readonly static Queue<Action> ExecuteOnMainThread = new Queue<Action>();
|
||
|
|
||
|
// Attach a text component to get some debug status info.
|
||
|
public Text statusText;
|
||
|
|
||
|
/// <summary>
|
||
|
/// Video type.
|
||
|
/// </summary>
|
||
|
public enum VideoType {
|
||
|
Dash = 0,
|
||
|
HLS = 2,
|
||
|
Other = 3
|
||
|
};
|
||
|
|
||
|
public enum VideoResolution {
|
||
|
Lowest = 1,
|
||
|
_720 = 720,
|
||
|
_1080 = 1080,
|
||
|
_2048 = 2048,
|
||
|
Highest = 4096
|
||
|
};
|
||
|
|
||
|
/// <summary>
|
||
|
/// Video player state.
|
||
|
/// </summary>
|
||
|
public enum VideoPlayerState {
|
||
|
Idle = 1,
|
||
|
Preparing = 2,
|
||
|
Buffering = 3,
|
||
|
Ready = 4,
|
||
|
Ended = 5
|
||
|
};
|
||
|
|
||
|
public enum VideoEvents {
|
||
|
VideoReady = 1,
|
||
|
VideoStartPlayback = 2,
|
||
|
VideoFormatChanged = 3,
|
||
|
VideoSurfaceSet = 4,
|
||
|
VideoSizeChanged = 5
|
||
|
};
|
||
|
|
||
|
/// <summary>
|
||
|
/// Plugin render commands.
|
||
|
/// </summary>
|
||
|
/// <remarks>
|
||
|
/// These are added to the eventbase for the specific player object and
|
||
|
/// issued to the plugin.
|
||
|
/// </remarks>
|
||
|
private enum RenderCommand {
|
||
|
None = -1,
|
||
|
InitializePlayer = 0,
|
||
|
UpdateVideo = 1,
|
||
|
RenderMono = 2,
|
||
|
RenderLeftEye = 3,
|
||
|
RenderRightEye = 4,
|
||
|
Shutdown = 5
|
||
|
};
|
||
|
|
||
|
// The circular buffer has to be at least 2,
|
||
|
// but in some cases that is too small, so set some reasonable range
|
||
|
// so a slider shows up in the property inspector.
|
||
|
[Range(MIN_BUFFER_SIZE, MAX_BUFFER_SIZE)]
|
||
|
public int bufferSize;
|
||
|
|
||
|
/// <summary>
|
||
|
/// The type of the video.
|
||
|
/// </summary>
|
||
|
public VideoType videoType;
|
||
|
public string videoURL;
|
||
|
public string videoContentID;
|
||
|
public string videoProviderId;
|
||
|
|
||
|
public VideoResolution initialResolution = VideoResolution.Highest;
|
||
|
|
||
|
/// <summary>
|
||
|
/// True for adjusting the aspect ratio of the renderer.
|
||
|
/// </summary>
|
||
|
public bool adjustAspectRatio;
|
||
|
|
||
|
/// <summary>
|
||
|
/// The use secure path for DRM protected video.
|
||
|
/// </summary>
|
||
|
public bool useSecurePath;
|
||
|
|
||
|
public bool VideoReady {
|
||
|
get {
|
||
|
return videoPlayerPtr != IntPtr.Zero && IsVideoReady(videoPlayerPtr);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public long CurrentPosition {
|
||
|
get {
|
||
|
return videoPlayerPtr != IntPtr.Zero ? GetCurrentPosition(videoPlayerPtr) : 0;
|
||
|
}
|
||
|
set {
|
||
|
// If the position is being set to 0, reset the framecount as well.
|
||
|
// This allows the texture swapping to work correctly at the beginning
|
||
|
// of the stream.
|
||
|
if (value == 0) {
|
||
|
framecount = 0;
|
||
|
}
|
||
|
|
||
|
SetCurrentPosition(videoPlayerPtr, value);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public long VideoDuration {
|
||
|
get {
|
||
|
return videoPlayerPtr != IntPtr.Zero ? GetDuration(videoPlayerPtr) : 0;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public long BufferedPosition {
|
||
|
get {
|
||
|
return videoPlayerPtr != IntPtr.Zero ? GetBufferedPosition(videoPlayerPtr) : 0;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public int BufferedPercentage {
|
||
|
get {
|
||
|
return videoPlayerPtr != IntPtr.Zero ? GetBufferedPercentage(videoPlayerPtr) : 0;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public bool IsPaused {
|
||
|
get {
|
||
|
return !initialized || videoPlayerPtr == IntPtr.Zero || IsVideoPaused(videoPlayerPtr);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public VideoPlayerState PlayerState {
|
||
|
get {
|
||
|
return videoPlayerPtr != IntPtr.Zero ? (VideoPlayerState)GetPlayerState(videoPlayerPtr) : VideoPlayerState.Idle;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public int MaxVolume {
|
||
|
get {
|
||
|
return videoPlayerPtr != IntPtr.Zero ? GetMaxVolume(videoPlayerPtr) : 0;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public int CurrentVolume {
|
||
|
get {
|
||
|
return videoPlayerPtr != IntPtr.Zero ? GetCurrentVolume(videoPlayerPtr) : 0;
|
||
|
}
|
||
|
set {
|
||
|
SetCurrentVolume(value);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// Create the video player instance and the event base id.
|
||
|
void Awake() {
|
||
|
// Find the components on which to set the video texture.
|
||
|
graphicComponent = GetComponent<Graphic>();
|
||
|
rendererComponent = GetComponent<Renderer>();
|
||
|
|
||
|
CreatePlayer();
|
||
|
}
|
||
|
|
||
|
void CreatePlayer() {
|
||
|
bufferSize = bufferSize < MIN_BUFFER_SIZE ? MIN_BUFFER_SIZE : bufferSize;
|
||
|
if (videoTextures != null) {
|
||
|
DestroyVideoTextures();
|
||
|
}
|
||
|
videoTextures = new Texture2D[bufferSize];
|
||
|
currentTexture = 0;
|
||
|
videoPlayerPtr = CreateVideoPlayer();
|
||
|
videoPlayerEventBase = GetVideoPlayerEventBase(videoPlayerPtr);
|
||
|
Debug.Log(" -- " + gameObject.name + " created with base " +
|
||
|
videoPlayerEventBase);
|
||
|
|
||
|
SetOnVideoEventCallback((eventId) => {
|
||
|
Debug.Log("------------- E V E N T " + eventId + " -----------------");
|
||
|
UpdateStatusText();
|
||
|
});
|
||
|
|
||
|
SetOnExceptionCallback((type, msg) => {
|
||
|
Debug.LogError("Exception: " + type + ": " + msg);
|
||
|
});
|
||
|
|
||
|
initialized = false;
|
||
|
|
||
|
if (rendererComponent != null) {
|
||
|
initialTexture = rendererComponent.material.mainTexture;
|
||
|
} else if (graphicComponent) {
|
||
|
initialTexture = graphicComponent.mainTexture;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
IEnumerator Start() {
|
||
|
CreateTextureForVideoMaybe();
|
||
|
renderEventFunction = GetRenderEventFunc();
|
||
|
if (renderEventFunction != IntPtr.Zero) {
|
||
|
IssuePlayerEvent(RenderCommand.InitializePlayer);
|
||
|
yield return StartCoroutine(CallPluginAtEndOfFrames());
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void OnDisable() {
|
||
|
if (videoPlayerPtr != IntPtr.Zero) {
|
||
|
if (GetPlayerState(videoPlayerPtr) == (int)VideoPlayerState.Ready) {
|
||
|
PauseVideo(videoPlayerPtr);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Sets the display texture.
|
||
|
/// </summary>
|
||
|
/// <param name="texture">Texture to display.
|
||
|
// If null, the initial texture of the renderer is used.</param>
|
||
|
public void SetDisplayTexture(Texture texture) {
|
||
|
if (texture == null) {
|
||
|
texture = initialTexture;
|
||
|
}
|
||
|
|
||
|
if (texture == null) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (rendererComponent != null) {
|
||
|
rendererComponent.sharedMaterial.mainTexture = initialTexture;
|
||
|
} else if (graphicComponent != null) {
|
||
|
graphicComponent.material.mainTexture = initialTexture;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public void CleanupVideo() {
|
||
|
Debug.Log("Cleaning Up video!");
|
||
|
exitProcessing = true;
|
||
|
if (videoPlayerPtr != IntPtr.Zero) {
|
||
|
DestroyVideoPlayer(videoPlayerPtr);
|
||
|
videoPlayerPtr = IntPtr.Zero;
|
||
|
}
|
||
|
DestroyVideoTextures();
|
||
|
if (rendererComponent != null) {
|
||
|
rendererComponent.sharedMaterial.mainTexture = initialTexture;
|
||
|
} else if (graphicComponent != null) {
|
||
|
graphicComponent.material.mainTexture = initialTexture;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public void ReInitializeVideo() {
|
||
|
if (rendererComponent != null) {
|
||
|
rendererComponent.sharedMaterial.mainTexture = initialTexture;
|
||
|
} else if (graphicComponent != null) {
|
||
|
graphicComponent.material.mainTexture = initialTexture;
|
||
|
}
|
||
|
|
||
|
if (videoPlayerPtr == IntPtr.Zero) {
|
||
|
CreatePlayer();
|
||
|
IssuePlayerEvent(RenderCommand.InitializePlayer);
|
||
|
}
|
||
|
if (Init()) {
|
||
|
StartCoroutine(CallPluginAtEndOfFrames());
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void DestroyVideoTextures() {
|
||
|
if (videoTextures != null) {
|
||
|
foreach (Texture2D t in videoTextures) {
|
||
|
if (t != null) {
|
||
|
// Free GPU memory immediately.
|
||
|
t.Resize(1, 1);
|
||
|
t.Apply();
|
||
|
// Unity's destroy is lazy.
|
||
|
Destroy(t);
|
||
|
}
|
||
|
}
|
||
|
videoTextures = null;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void OnEnable() {
|
||
|
if (videoPlayerPtr != IntPtr.Zero) {
|
||
|
StartCoroutine(CallPluginAtEndOfFrames());
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void OnDestroy() {
|
||
|
if (videoPlayerPtr != IntPtr.Zero) {
|
||
|
DestroyVideoPlayer(videoPlayerPtr);
|
||
|
}
|
||
|
DestroyVideoTextures();
|
||
|
}
|
||
|
|
||
|
void OnValidate() {
|
||
|
Renderer r = GetComponent<Renderer>();
|
||
|
Graphic g = GetComponent<Graphic>();
|
||
|
if (g == null && r == null) {
|
||
|
Debug.LogError("TexturePlayer object must have either " +
|
||
|
"a Renderer component or a Graphic component.");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void OnApplicationPause(bool bPause) {
|
||
|
if (videoPlayerPtr != IntPtr.Zero) {
|
||
|
if (bPause) {
|
||
|
playOnResume = !IsPaused;
|
||
|
PauseVideo(videoPlayerPtr);
|
||
|
} else {
|
||
|
if (playOnResume) {
|
||
|
PlayVideo(videoPlayerPtr);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void OnRenderObject() {
|
||
|
|
||
|
// Don't render if not initialized.
|
||
|
if (videoPlayerPtr == IntPtr.Zero || videoTextures[0] == null) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
Texture newTex = videoTextures[currentTexture];
|
||
|
|
||
|
// Handle either the renderer component or the graphic component.
|
||
|
if (rendererComponent != null) {
|
||
|
|
||
|
// Don't render the first texture from the player, it is unitialized.
|
||
|
if (currentTexture <= 1 && framecount <= 1) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// Don't swap the textures if the video ended.
|
||
|
if (PlayerState == VideoPlayerState.Ended) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// Unity may build new a new material instance when assigning
|
||
|
// material.x which can lead to duplicating materials each frame
|
||
|
// whereas using the shared material will modify the original material.
|
||
|
if (rendererComponent.material.mainTexture != null) {
|
||
|
IntPtr currentTexId =
|
||
|
rendererComponent.sharedMaterial.mainTexture.GetNativeTexturePtr();
|
||
|
|
||
|
// Update the material's texture if it is different.
|
||
|
if (currentTexId != newTex.GetNativeTexturePtr()) {
|
||
|
rendererComponent.sharedMaterial.mainTexture = newTex;
|
||
|
framecount += 1f;
|
||
|
}
|
||
|
} else {
|
||
|
rendererComponent.sharedMaterial.mainTexture = newTex;
|
||
|
}
|
||
|
|
||
|
} else if (graphicComponent != null) {
|
||
|
if (graphicComponent.material.mainTexture != null) {
|
||
|
IntPtr currentTexId =
|
||
|
graphicComponent.material.mainTexture.GetNativeTexturePtr();
|
||
|
|
||
|
// Update the material's texture if it is different.
|
||
|
if (currentTexId != newTex.GetNativeTexturePtr()) {
|
||
|
graphicComponent.material.mainTexture = newTex;
|
||
|
framecount += 1f;
|
||
|
}
|
||
|
} else {
|
||
|
graphicComponent.material.mainTexture = newTex;
|
||
|
}
|
||
|
} else {
|
||
|
Debug.LogError("GvrVideoPlayerTexture: No render or graphic component.");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private void OnRestartVideoEvent(int eventId) {
|
||
|
if (eventId == (int)VideoEvents.VideoReady) {
|
||
|
Debug.Log("Restarting video complete.");
|
||
|
RemoveOnVideoEventCallback(OnRestartVideoEvent);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Resets the video player.
|
||
|
/// </summary>
|
||
|
public void RestartVideo() {
|
||
|
SetOnVideoEventCallback(OnRestartVideoEvent);
|
||
|
|
||
|
string theUrl = ProcessURL();
|
||
|
|
||
|
InitVideoPlayer(videoPlayerPtr, (int) videoType, theUrl,
|
||
|
videoContentID,
|
||
|
videoProviderId,
|
||
|
useSecurePath,
|
||
|
true);
|
||
|
framecount = 0;
|
||
|
}
|
||
|
|
||
|
public void SetCurrentVolume(int val) {
|
||
|
SetCurrentVolume(videoPlayerPtr, val);
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Initialize the video player.
|
||
|
/// </summary>
|
||
|
/// <returns>true if successful</returns>
|
||
|
public bool Init() {
|
||
|
if (initialized) {
|
||
|
Debug.Log("Skipping initialization: video player already loaded");
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
if (videoURL == null || videoURL.Length == 0) {
|
||
|
Debug.LogError("Cannot initialize with null videoURL");
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
videoURL = videoURL == null ? "" : videoURL.Trim();
|
||
|
videoContentID = videoContentID == null ? "" : videoContentID.Trim();
|
||
|
videoProviderId = videoProviderId == null ? "" : videoProviderId.Trim();
|
||
|
|
||
|
SetInitialResolution(videoPlayerPtr, (int) initialResolution);
|
||
|
|
||
|
string theUrl = ProcessURL();
|
||
|
Debug.Log("Playing " + videoType + " " + theUrl);
|
||
|
Debug.Log("videoContentID = " + videoContentID);
|
||
|
Debug.Log("videoProviderId = " + videoProviderId);
|
||
|
videoPlayerPtr = InitVideoPlayer(videoPlayerPtr, (int) videoType, theUrl,
|
||
|
videoContentID, videoProviderId,
|
||
|
useSecurePath, false);
|
||
|
initialized = true;
|
||
|
framecount = 0;
|
||
|
return videoPlayerPtr != IntPtr.Zero;
|
||
|
}
|
||
|
|
||
|
public bool Play() {
|
||
|
if (!initialized) {
|
||
|
Init();
|
||
|
} else if (!processingRunning) {
|
||
|
StartCoroutine(CallPluginAtEndOfFrames());
|
||
|
}
|
||
|
if (videoPlayerPtr != IntPtr.Zero && IsVideoReady(videoPlayerPtr)) {
|
||
|
return PlayVideo(videoPlayerPtr) == 0;
|
||
|
} else {
|
||
|
Debug.LogError("Video player not ready to Play!");
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public bool Pause() {
|
||
|
if (!initialized) {
|
||
|
Init();
|
||
|
}
|
||
|
if (VideoReady) {
|
||
|
return PauseVideo(videoPlayerPtr) == 0;
|
||
|
} else {
|
||
|
Debug.LogError("Video player not ready to Pause!");
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Adjusts the aspect ratio.
|
||
|
/// </summary>
|
||
|
/// <remarks>
|
||
|
/// This adjusts the transform scale to match the aspect
|
||
|
/// ratio of the texture.
|
||
|
/// </remarks>
|
||
|
private void AdjustAspectRatio() {
|
||
|
float aspectRatio = texWidth / texHeight;
|
||
|
|
||
|
// set the y scale based on the x value
|
||
|
Vector3 newscale = transform.localScale;
|
||
|
newscale.y = Mathf.Min(newscale.y, newscale.x / aspectRatio);
|
||
|
|
||
|
transform.localScale = newscale;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Creates the texture for video if needed.
|
||
|
/// </summary>
|
||
|
private void CreateTextureForVideoMaybe() {
|
||
|
if (videoTextures[0] == null || (texWidth != videoTextures[0].width ||
|
||
|
texHeight != videoTextures[0].height)) {
|
||
|
|
||
|
// Check the dimensions to make sure they are valid.
|
||
|
if (texWidth < 0 || texHeight < 0) {
|
||
|
// Maybe use the last dimension. This happens when re-initializing the player.
|
||
|
if (videoTextures != null && videoTextures[0].width > 0) {
|
||
|
texWidth = videoTextures[0].width;
|
||
|
texHeight = videoTextures[0].height;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
int[] tex_ids = new int[videoTextures.Length];
|
||
|
for (int idx = 0; idx < videoTextures.Length; idx++) {
|
||
|
// Resize the existing texture if there, otherwise create it.
|
||
|
if (videoTextures[idx] != null) {
|
||
|
if (videoTextures[idx].width != texWidth
|
||
|
|| videoTextures[idx].height != texHeight)
|
||
|
{
|
||
|
videoTextures[idx].Resize(texWidth, texHeight);
|
||
|
videoTextures[idx].Apply();
|
||
|
}
|
||
|
} else {
|
||
|
videoTextures[idx] = new Texture2D(texWidth, texHeight,
|
||
|
TextureFormat.RGBA32, false);
|
||
|
videoTextures[idx].filterMode = FilterMode.Bilinear;
|
||
|
videoTextures[idx].wrapMode = TextureWrapMode.Clamp;
|
||
|
}
|
||
|
|
||
|
tex_ids[idx] = videoTextures[idx].GetNativeTexturePtr().ToInt32();
|
||
|
}
|
||
|
|
||
|
SetExternalTextures(videoPlayerPtr, tex_ids, tex_ids.Length,
|
||
|
texWidth, texHeight);
|
||
|
currentTexture = 0;
|
||
|
UpdateStatusText();
|
||
|
}
|
||
|
|
||
|
if (adjustAspectRatio) {
|
||
|
AdjustAspectRatio();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private void UpdateStatusText() {
|
||
|
float fps = CurrentPosition > 0 ?
|
||
|
(framecount / (CurrentPosition / 1000f)) : CurrentPosition;
|
||
|
string status = texWidth + " x " + texHeight + " buffer: " +
|
||
|
(BufferedPosition / 1000) + " " + PlayerState + " fps: " + fps;
|
||
|
if (statusText != null) {
|
||
|
if (statusText.text != status) {
|
||
|
statusText.text = status;
|
||
|
Debug.Log("STATUS: " + status);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Issues the player event.
|
||
|
/// </summary>
|
||
|
/// <param name="evt">The event to send to the video player
|
||
|
/// instance.
|
||
|
/// </param>
|
||
|
private void IssuePlayerEvent(RenderCommand evt) {
|
||
|
if (renderEventFunction != IntPtr.Zero && evt != RenderCommand.None) {
|
||
|
GL.IssuePluginEvent(renderEventFunction,
|
||
|
videoPlayerEventBase + (int) evt);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void Update() {
|
||
|
while (ExecuteOnMainThread.Count > 0) {
|
||
|
ExecuteOnMainThread.Dequeue().Invoke();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private IEnumerator CallPluginAtEndOfFrames() {
|
||
|
if (processingRunning) {
|
||
|
Debug.LogError("CallPluginAtEndOfFrames invoked while already running.");
|
||
|
yield break;
|
||
|
}
|
||
|
|
||
|
// Only run while the video is playing.
|
||
|
bool running = true;
|
||
|
processingRunning = true;
|
||
|
exitProcessing = false;
|
||
|
WaitForEndOfFrame wfeof = new WaitForEndOfFrame();
|
||
|
while (running) {
|
||
|
// Wait until all frame rendering is done
|
||
|
yield return wfeof;
|
||
|
|
||
|
if (exitProcessing) {
|
||
|
running = false;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
if (videoPlayerPtr != IntPtr.Zero) {
|
||
|
CreateTextureForVideoMaybe();
|
||
|
}
|
||
|
|
||
|
IntPtr tex = GetRenderableTextureId(videoPlayerPtr);
|
||
|
currentTexture = 0;
|
||
|
for (int i = 0; i < videoTextures.Length; i++) {
|
||
|
if (tex == videoTextures[i].GetNativeTexturePtr()) {
|
||
|
currentTexture = i;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (!VideoReady) {
|
||
|
continue;
|
||
|
} else if (framecount > 1 && PlayerState == VideoPlayerState.Ended) {
|
||
|
running = false;
|
||
|
}
|
||
|
|
||
|
IssuePlayerEvent(RenderCommand.UpdateVideo);
|
||
|
IssuePlayerEvent(RenderCommand.RenderMono);
|
||
|
|
||
|
int w = GetWidth(videoPlayerPtr);
|
||
|
int h = GetHeight(videoPlayerPtr);
|
||
|
// Limit total pixel count to the same as 2160p.
|
||
|
// 3840 * 2160 == 2880 * 2880
|
||
|
if (w * h > 2880 * 2880) {
|
||
|
// Clamp the max resolution preserving aspect ratio.
|
||
|
float aspectRoot = (float) Math.Sqrt(w / h);
|
||
|
w = (int) (2880 * aspectRoot);
|
||
|
h = (int) (2880 / aspectRoot);
|
||
|
}
|
||
|
texWidth = w;
|
||
|
texHeight = h;
|
||
|
|
||
|
if ((int) framecount % 30 == 0) {
|
||
|
UpdateStatusText();
|
||
|
}
|
||
|
|
||
|
long bp = BufferedPosition;
|
||
|
if (bp != lastBufferedPosition) {
|
||
|
lastBufferedPosition = bp;
|
||
|
UpdateStatusText();
|
||
|
}
|
||
|
}
|
||
|
processingRunning = false;
|
||
|
}
|
||
|
|
||
|
public void RemoveOnVideoEventCallback(Action<int> callback) {
|
||
|
if (onEventCallbacks != null) {
|
||
|
onEventCallbacks.Remove(callback);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public void SetOnVideoEventCallback(Action<int> callback) {
|
||
|
if (onEventCallbacks == null) {
|
||
|
onEventCallbacks = new List<Action<int>>();
|
||
|
}
|
||
|
onEventCallbacks.Add(callback);
|
||
|
SetOnVideoEventCallback(videoPlayerPtr, InternalOnVideoEventCallback,
|
||
|
ToIntPtr(this));
|
||
|
}
|
||
|
|
||
|
internal void FireVideoEvent(int eventId) {
|
||
|
if (onEventCallbacks == null) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// Copy the collection so the callbacks can remove themselves from the list.
|
||
|
Action<int>[] cblist = onEventCallbacks.ToArray();
|
||
|
foreach (Action<int> cb in cblist) {
|
||
|
try {
|
||
|
cb(eventId);
|
||
|
} catch (Exception e) {
|
||
|
Debug.LogError("exception calling callback: " + e);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
[AOT.MonoPInvokeCallback(typeof(OnVideoEventCallback))]
|
||
|
static void InternalOnVideoEventCallback(IntPtr cbdata, int eventId) {
|
||
|
if (cbdata == IntPtr.Zero) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
GvrVideoPlayerTexture player;
|
||
|
var gcHandle = GCHandle.FromIntPtr(cbdata);
|
||
|
try {
|
||
|
player = (GvrVideoPlayerTexture) gcHandle.Target;
|
||
|
}
|
||
|
catch (InvalidCastException e) {
|
||
|
Debug.LogError("GC Handle pointed to unexpected type: " +
|
||
|
gcHandle.Target + ". Expected " +
|
||
|
typeof(GvrVideoPlayerTexture));
|
||
|
throw e;
|
||
|
}
|
||
|
|
||
|
if (player != null) {
|
||
|
ExecuteOnMainThread.Enqueue(() => player.FireVideoEvent(eventId));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public void SetOnExceptionCallback(Action<string, string> callback) {
|
||
|
if (onExceptionCallbacks == null) {
|
||
|
onExceptionCallbacks = new List<Action<string, string>>();
|
||
|
SetOnExceptionCallback(videoPlayerPtr, InternalOnExceptionCallback,
|
||
|
ToIntPtr(this));
|
||
|
}
|
||
|
onExceptionCallbacks.Add(callback);
|
||
|
}
|
||
|
|
||
|
|
||
|
[AOT.MonoPInvokeCallback(typeof(OnExceptionCallback))]
|
||
|
static void InternalOnExceptionCallback(string type, string msg,
|
||
|
IntPtr cbdata) {
|
||
|
if (cbdata == IntPtr.Zero) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
GvrVideoPlayerTexture player;
|
||
|
var gcHandle = GCHandle.FromIntPtr(cbdata);
|
||
|
try {
|
||
|
player = (GvrVideoPlayerTexture) gcHandle.Target;
|
||
|
}
|
||
|
catch (InvalidCastException e) {
|
||
|
Debug.LogError("GC Handle pointed to unexpected type: " +
|
||
|
gcHandle.Target + ". Expected " +
|
||
|
typeof(GvrVideoPlayerTexture));
|
||
|
throw e;
|
||
|
}
|
||
|
|
||
|
if (player != null) {
|
||
|
ExecuteOnMainThread.Enqueue(() => player.FireOnException(type, msg));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
internal void FireOnException(string type, string msg) {
|
||
|
if (onExceptionCallbacks == null) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
foreach (Action<string, string> cb in onExceptionCallbacks) {
|
||
|
try {
|
||
|
cb(type, msg);
|
||
|
} catch (Exception e) {
|
||
|
Debug.LogError("exception calling callback: " + e);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
internal static IntPtr ToIntPtr(System.Object obj) {
|
||
|
GCHandle handle = GCHandle.Alloc(obj);
|
||
|
return GCHandle.ToIntPtr(handle);
|
||
|
}
|
||
|
|
||
|
internal string ProcessURL() {
|
||
|
return videoURL.Replace("${Application.dataPath}", Application.dataPath);
|
||
|
}
|
||
|
|
||
|
internal delegate void OnVideoEventCallback(IntPtr cbdata, int eventId);
|
||
|
|
||
|
internal delegate void OnExceptionCallback(string type, string msg,
|
||
|
IntPtr cbdata);
|
||
|
|
||
|
#if UNITY_ANDROID && !UNITY_EDITOR
|
||
|
private const string dllName = "gvrvideo";
|
||
|
[DllImport(dllName)]
|
||
|
private static extern IntPtr GetRenderEventFunc();
|
||
|
|
||
|
[DllImport(dllName)]
|
||
|
private static extern void SetExternalTextures(IntPtr videoPlayerPtr,
|
||
|
int[] texIds,
|
||
|
int size,
|
||
|
int w,
|
||
|
int h);
|
||
|
|
||
|
[DllImport(dllName)]
|
||
|
private static extern IntPtr GetRenderableTextureId(IntPtr videoPlayerPtr);
|
||
|
|
||
|
// Keep public so we can check for the dll being present at runtime.
|
||
|
[DllImport(dllName)]
|
||
|
public static extern IntPtr CreateVideoPlayer();
|
||
|
|
||
|
// Keep public so we can check for the dll being present at runtime.
|
||
|
[DllImport(dllName)]
|
||
|
public static extern void DestroyVideoPlayer(IntPtr videoPlayerPtr);
|
||
|
|
||
|
[DllImport(dllName)]
|
||
|
private static extern int GetVideoPlayerEventBase(IntPtr videoPlayerPtr);
|
||
|
|
||
|
[DllImport(dllName)]
|
||
|
private static extern IntPtr InitVideoPlayer(IntPtr videoPlayerPtr,
|
||
|
int videoType,
|
||
|
string videoURL,
|
||
|
string contentID,
|
||
|
string providerId,
|
||
|
bool useSecurePath,
|
||
|
bool useExisting);
|
||
|
|
||
|
[DllImport(dllName)]
|
||
|
private static extern void SetInitialResolution(IntPtr videoPlayerPtr,
|
||
|
int initialResolution);
|
||
|
|
||
|
[DllImport(dllName)]
|
||
|
private static extern int GetPlayerState(IntPtr videoPlayerPtr);
|
||
|
|
||
|
[DllImport(dllName)]
|
||
|
private static extern int GetWidth(IntPtr videoPlayerPtr);
|
||
|
|
||
|
[DllImport(dllName)]
|
||
|
private static extern int GetHeight(IntPtr videoPlayerPtr);
|
||
|
|
||
|
[DllImport(dllName)]
|
||
|
private static extern int PlayVideo(IntPtr videoPlayerPtr);
|
||
|
|
||
|
[DllImport(dllName)]
|
||
|
private static extern int PauseVideo(IntPtr videoPlayerPtr);
|
||
|
|
||
|
[DllImport(dllName)]
|
||
|
private static extern bool IsVideoReady(IntPtr videoPlayerPtr);
|
||
|
|
||
|
[DllImport(dllName)]
|
||
|
private static extern bool IsVideoPaused(IntPtr videoPlayerPtr);
|
||
|
|
||
|
[DllImport(dllName)]
|
||
|
private static extern long GetDuration(IntPtr videoPlayerPtr);
|
||
|
|
||
|
[DllImport(dllName)]
|
||
|
private static extern long GetBufferedPosition(IntPtr videoPlayerPtr);
|
||
|
|
||
|
[DllImport(dllName)]
|
||
|
private static extern long GetCurrentPosition(IntPtr videoPlayerPtr);
|
||
|
|
||
|
[DllImport(dllName)]
|
||
|
private static extern void SetCurrentPosition(IntPtr videoPlayerPtr,
|
||
|
long pos);
|
||
|
|
||
|
[DllImport(dllName)]
|
||
|
private static extern int GetBufferedPercentage(IntPtr videoPlayerPtr);
|
||
|
|
||
|
[DllImport(dllName)]
|
||
|
private static extern int GetMaxVolume(IntPtr videoPlayerPtr);
|
||
|
|
||
|
[DllImport(dllName)]
|
||
|
private static extern int GetCurrentVolume(IntPtr videoPlayerPtr);
|
||
|
|
||
|
[DllImport(dllName)]
|
||
|
private static extern void SetCurrentVolume(IntPtr videoPlayerPtr,
|
||
|
int value);
|
||
|
|
||
|
[DllImport(dllName)]
|
||
|
private static extern bool SetVideoPlayerSupportClassname(
|
||
|
IntPtr videoPlayerPtr,
|
||
|
string classname);
|
||
|
|
||
|
[DllImport(dllName)]
|
||
|
private static extern IntPtr GetRawPlayer(IntPtr videoPlayerPtr);
|
||
|
|
||
|
[DllImport(dllName)]
|
||
|
private static extern void SetOnVideoEventCallback(IntPtr videoPlayerPtr,
|
||
|
OnVideoEventCallback callback,
|
||
|
IntPtr callback_arg);
|
||
|
|
||
|
[DllImport(dllName)]
|
||
|
private static extern void SetOnExceptionCallback(IntPtr videoPlayerPtr,
|
||
|
OnExceptionCallback callback,
|
||
|
IntPtr callback_arg);
|
||
|
#else
|
||
|
private const string NOT_IMPLEMENTED_MSG =
|
||
|
"Not implemented on this platform";
|
||
|
|
||
|
private static IntPtr GetRenderEventFunc() {
|
||
|
Debug.Log(NOT_IMPLEMENTED_MSG);
|
||
|
return IntPtr.Zero;
|
||
|
}
|
||
|
|
||
|
private static void SetExternalTextures(IntPtr videoPlayerPtr,
|
||
|
int[] texIds,
|
||
|
int size,
|
||
|
int w,
|
||
|
int h) {
|
||
|
Debug.Log(NOT_IMPLEMENTED_MSG);
|
||
|
}
|
||
|
|
||
|
private static IntPtr GetRenderableTextureId(IntPtr videoPlayerPtr) {
|
||
|
return IntPtr.Zero;
|
||
|
}
|
||
|
|
||
|
// Make this public so we can test the loading of the DLL.
|
||
|
public static IntPtr CreateVideoPlayer() {
|
||
|
Debug.Log(NOT_IMPLEMENTED_MSG);
|
||
|
return IntPtr.Zero;
|
||
|
}
|
||
|
|
||
|
|
||
|
// Make this public so we can test the loading of the DLL.
|
||
|
public static void DestroyVideoPlayer(IntPtr videoPlayerPtr) {
|
||
|
Debug.Log(NOT_IMPLEMENTED_MSG);
|
||
|
}
|
||
|
|
||
|
|
||
|
private static int GetVideoPlayerEventBase(IntPtr videoPlayerPtr) {
|
||
|
Debug.Log(NOT_IMPLEMENTED_MSG);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
private static IntPtr InitVideoPlayer(IntPtr videoPlayerPtr, int videoType,
|
||
|
string videoURL,
|
||
|
string contentID,
|
||
|
string providerId,
|
||
|
bool useSecurePath,
|
||
|
bool useExisting) {
|
||
|
Debug.Log(NOT_IMPLEMENTED_MSG);
|
||
|
return IntPtr.Zero;
|
||
|
}
|
||
|
|
||
|
private static void SetInitialResolution(IntPtr videoPlayerPtr,
|
||
|
int initialResolution) {
|
||
|
Debug.Log(NOT_IMPLEMENTED_MSG);
|
||
|
}
|
||
|
|
||
|
private static int GetPlayerState(IntPtr videoPlayerPtr) {
|
||
|
Debug.Log(NOT_IMPLEMENTED_MSG);
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
private static int GetWidth(IntPtr videoPlayerPtr) {
|
||
|
Debug.Log(NOT_IMPLEMENTED_MSG);
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
private static int GetHeight(IntPtr videoPlayerPtr) {
|
||
|
Debug.Log(NOT_IMPLEMENTED_MSG);
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
private static int PlayVideo(IntPtr videoPlayerPtr) {
|
||
|
Debug.Log(NOT_IMPLEMENTED_MSG);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
|
||
|
private static int PauseVideo(IntPtr videoPlayerPtr) {
|
||
|
Debug.Log(NOT_IMPLEMENTED_MSG);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
private static bool IsVideoReady(IntPtr videoPlayerPtr) {
|
||
|
Debug.Log(NOT_IMPLEMENTED_MSG);
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
private static bool IsVideoPaused(IntPtr videoPlayerPtr) {
|
||
|
Debug.Log(NOT_IMPLEMENTED_MSG);
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
private static long GetDuration(IntPtr videoPlayerPtr) {
|
||
|
Debug.Log(NOT_IMPLEMENTED_MSG);
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
private static long GetBufferedPosition(IntPtr videoPlayerPtr) {
|
||
|
Debug.Log(NOT_IMPLEMENTED_MSG);
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
private static long GetCurrentPosition(IntPtr videoPlayerPtr) {
|
||
|
Debug.Log(NOT_IMPLEMENTED_MSG);
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
private static void SetCurrentPosition(IntPtr videoPlayerPtr, long pos) {
|
||
|
Debug.Log(NOT_IMPLEMENTED_MSG);
|
||
|
}
|
||
|
|
||
|
private static int GetBufferedPercentage(IntPtr videoPlayerPtr) {
|
||
|
Debug.Log(NOT_IMPLEMENTED_MSG);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
private static int GetMaxVolume(IntPtr videoPlayerPtr) {
|
||
|
Debug.Log(NOT_IMPLEMENTED_MSG);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
private static int GetCurrentVolume(IntPtr videoPlayerPtr) {
|
||
|
Debug.Log(NOT_IMPLEMENTED_MSG);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
private static void SetCurrentVolume(IntPtr videoPlayerPtr, int value) {
|
||
|
Debug.Log(NOT_IMPLEMENTED_MSG);
|
||
|
}
|
||
|
|
||
|
private static bool SetVideoPlayerSupportClassname(IntPtr videoPlayerPtr,
|
||
|
string classname) {
|
||
|
Debug.Log(NOT_IMPLEMENTED_MSG);
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
private static IntPtr GetRawPlayer(IntPtr videoPlayerPtr) {
|
||
|
Debug.Log(NOT_IMPLEMENTED_MSG);
|
||
|
return IntPtr.Zero;
|
||
|
}
|
||
|
|
||
|
private static void SetOnVideoEventCallback(IntPtr videoPlayerPtr,
|
||
|
OnVideoEventCallback callback,
|
||
|
IntPtr callback_arg) {
|
||
|
Debug.Log(NOT_IMPLEMENTED_MSG);
|
||
|
}
|
||
|
|
||
|
private static void SetOnExceptionCallback(IntPtr videoPlayerPtr,
|
||
|
OnExceptionCallback callback,
|
||
|
IntPtr callback_arg) {
|
||
|
Debug.Log(NOT_IMPLEMENTED_MSG);
|
||
|
}
|
||
|
#endif // UNITY_ANDROID && !UNITY_EDITOR
|
||
|
}
|
||
|
|