- Added trees outside the stable.
- Added a video player with horse songs preloaded. - Adjusted post-processing to be less harsh outdoors, and more visible indoors.
This commit is contained in:
@@ -0,0 +1,122 @@
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using VRC.SDK3.Components.Video;
|
||||
using VRC.SDK3.Video.Components;
|
||||
using VRC.SDKBase;
|
||||
|
||||
namespace UdonSharp.Video.Internal
|
||||
{
|
||||
/// <summary>
|
||||
/// Allows people to put in links to YouTube videos and other supported video services and have links just work
|
||||
/// Hooks into VRC's video player URL resolve callback and uses the VRC installation of YouTubeDL to resolve URLs in the editor.
|
||||
/// </summary>
|
||||
public static class EditorURLResolverShim
|
||||
{
|
||||
private static string _youtubeDLPath = "";
|
||||
private static HashSet<System.Diagnostics.Process> _runningYtdlProcesses = new HashSet<System.Diagnostics.Process>();
|
||||
private static HashSet<MonoBehaviour> _registeredBehaviours = new HashSet<MonoBehaviour>();
|
||||
|
||||
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterSceneLoad)]
|
||||
private static void SetupURLResolveCallback()
|
||||
{
|
||||
string[] splitPath = Application.persistentDataPath.Split('/', '\\');
|
||||
_youtubeDLPath = string.Join("\\", splitPath.Take(splitPath.Length - 2)) + "\\VRChat\\VRChat\\Tools\\yt-dlp.exe";
|
||||
|
||||
if (!File.Exists(_youtubeDLPath))
|
||||
{
|
||||
_youtubeDLPath = string.Join("\\", splitPath.Take(splitPath.Length - 2)) + "\\VRChat\\VRChat\\Tools\\youtube-dl.exe";
|
||||
}
|
||||
|
||||
if (!File.Exists(_youtubeDLPath))
|
||||
{
|
||||
Debug.LogWarning("[USharpVideo YTDL] Unable to find VRC YouTube-DL or YT-DLP installation, URLs will not be resolved in editor test your videos in game.");
|
||||
return;
|
||||
}
|
||||
|
||||
VRCUnityVideoPlayer.StartResolveURLCoroutine = ResolveURLCallback;
|
||||
EditorApplication.playModeStateChanged += PlayModeChanged;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cleans up any remaining YTDL processes from this play.
|
||||
/// In some cases VRC's YTDL has hung indefinitely eating CPU so this is a precaution against that potentially happening.
|
||||
/// </summary>
|
||||
/// <param name="change"></param>
|
||||
private static void PlayModeChanged(PlayModeStateChange change)
|
||||
{
|
||||
if (change == PlayModeStateChange.ExitingPlayMode)
|
||||
{
|
||||
foreach (var process in _runningYtdlProcesses)
|
||||
{
|
||||
if (!process.HasExited)
|
||||
{
|
||||
//Debug.Log("Closing YTDL process");
|
||||
process.Close();
|
||||
}
|
||||
}
|
||||
|
||||
_runningYtdlProcesses.Clear();
|
||||
|
||||
// Apparently the URLResolveCoroutine will run after this method in some cases magically. So don't because the process will throw an exception.
|
||||
foreach (MonoBehaviour behaviour in _registeredBehaviours)
|
||||
behaviour.StopAllCoroutines();
|
||||
|
||||
_registeredBehaviours.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
private static void ResolveURLCallback(VRCUrl url, int resolution, UnityEngine.Object videoPlayer, Action<string> urlResolvedCallback, Action<VideoError> errorCallback)
|
||||
{
|
||||
// Broken for some unknown reason, when multiple rate limits fire off, only fires the first callback.
|
||||
//if ((System.DateTime.UtcNow - lastRequestTime).TotalSeconds < 5.0)
|
||||
//{
|
||||
// Debug.LogWarning("Rate limited " + videoPlayer, videoPlayer);
|
||||
// errorCallback(VideoError.RateLimited);
|
||||
// return;
|
||||
//}
|
||||
|
||||
var ytdlProcess = new System.Diagnostics.Process();
|
||||
|
||||
ytdlProcess.StartInfo.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden;
|
||||
ytdlProcess.StartInfo.CreateNoWindow = true;
|
||||
ytdlProcess.StartInfo.UseShellExecute = false;
|
||||
ytdlProcess.StartInfo.RedirectStandardOutput = true;
|
||||
ytdlProcess.StartInfo.FileName = _youtubeDLPath;
|
||||
ytdlProcess.StartInfo.Arguments = $"--no-check-certificate --no-cache-dir --rm-cache-dir -f \"mp4[height<=?{resolution}]/best[height<=?{resolution}]\" --get-url \"{url}\"";
|
||||
|
||||
Debug.Log($"[<color=#9C6994>USharpVideo YTDL</color>] Attempting to resolve URL '{url}'");
|
||||
|
||||
ytdlProcess.Start();
|
||||
_runningYtdlProcesses.Add(ytdlProcess);
|
||||
|
||||
((MonoBehaviour)videoPlayer).StartCoroutine(URLResolveCoroutine(url.ToString(), ytdlProcess, videoPlayer, urlResolvedCallback, errorCallback));
|
||||
|
||||
_registeredBehaviours.Add((MonoBehaviour)videoPlayer);
|
||||
}
|
||||
|
||||
private static IEnumerator URLResolveCoroutine(string originalUrl, System.Diagnostics.Process ytdlProcess, UnityEngine.Object videoPlayer, Action<string> urlResolvedCallback, Action<VideoError> errorCallback)
|
||||
{
|
||||
while (!ytdlProcess.HasExited)
|
||||
yield return new WaitForSeconds(0.1f);
|
||||
|
||||
_runningYtdlProcesses.Remove(ytdlProcess);
|
||||
|
||||
string resolvedURL = ytdlProcess.StandardOutput.ReadLine();
|
||||
|
||||
// If a URL fails to resolve, YTDL will send error to stderror and nothing will be output to stdout
|
||||
if (string.IsNullOrEmpty(resolvedURL))
|
||||
errorCallback(VideoError.InvalidURL);
|
||||
else
|
||||
{
|
||||
Debug.Log($"[<color=#9C6994>USharpVideo YTDL</color>] Successfully resolved URL '{originalUrl}' to '{resolvedURL}'");
|
||||
urlResolvedCallback(resolvedURL);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c1b6679862355d4468ec4673a92a84b3
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,155 @@
|
||||
|
||||
using UnityEditor;
|
||||
using UdonSharpEditor;
|
||||
using UnityEditorInternal;
|
||||
using UnityEngine;
|
||||
using VRC.SDK3.Video.Components.AVPro;
|
||||
using System.Reflection;
|
||||
|
||||
#pragma warning disable CS0612 // Type or member is obsolete
|
||||
|
||||
namespace UdonSharp.Video.Internal
|
||||
{
|
||||
[CustomEditor(typeof(USharpVideoPlayer))]
|
||||
internal class USharpVideoInspector : Editor
|
||||
{
|
||||
ReorderableList playlistList;
|
||||
|
||||
SerializedProperty allowSeekProperty;
|
||||
SerializedProperty defaultUnlockedProperty;
|
||||
SerializedProperty allowCreatorControlProperty;
|
||||
|
||||
SerializedProperty syncFrequencyProperty;
|
||||
SerializedProperty syncThresholdProperty;
|
||||
|
||||
SerializedProperty defaultVolumeProperty;
|
||||
SerializedProperty audioRangeProperty;
|
||||
|
||||
SerializedProperty defaultStreamMode;
|
||||
|
||||
SerializedProperty playlistProperty;
|
||||
SerializedProperty loopPlaylistProperty;
|
||||
SerializedProperty shufflePlaylistProperty;
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
allowSeekProperty = serializedObject.FindProperty(nameof(USharpVideoPlayer.allowSeeking));
|
||||
defaultUnlockedProperty = serializedObject.FindProperty("defaultUnlocked");
|
||||
allowCreatorControlProperty = serializedObject.FindProperty(nameof(USharpVideoPlayer.allowInstanceCreatorControl));
|
||||
syncFrequencyProperty = serializedObject.FindProperty(nameof(USharpVideoPlayer.syncFrequency));
|
||||
syncThresholdProperty = serializedObject.FindProperty(nameof(USharpVideoPlayer.syncThreshold));
|
||||
|
||||
defaultVolumeProperty = serializedObject.FindProperty("defaultVolume");
|
||||
audioRangeProperty = serializedObject.FindProperty("audioRange");
|
||||
|
||||
defaultStreamMode = serializedObject.FindProperty("defaultStreamMode");
|
||||
|
||||
playlistProperty = serializedObject.FindProperty(nameof(USharpVideoPlayer.playlist));
|
||||
loopPlaylistProperty = serializedObject.FindProperty(nameof(USharpVideoPlayer.loopPlaylist));
|
||||
shufflePlaylistProperty = serializedObject.FindProperty(nameof(USharpVideoPlayer.shufflePlaylist));
|
||||
|
||||
playlistList = new ReorderableList(serializedObject, playlistProperty, true, true, true, true);
|
||||
playlistList.drawElementCallback = (Rect rect, int index, bool isActive, bool isFocused) =>
|
||||
{
|
||||
Rect testFieldRect = new Rect(rect.x, rect.y + 2, rect.width, EditorGUIUtility.singleLineHeight);
|
||||
|
||||
EditorGUI.PropertyField(testFieldRect, playlistList.serializedProperty.GetArrayElementAtIndex(index), label: new GUIContent());
|
||||
};
|
||||
playlistList.drawHeaderCallback = (Rect rect) => { EditorGUI.LabelField(rect, new GUIContent("Default Playlist URLs", "URLs that will play in sequence when you join the world until someone puts in a video.")); };
|
||||
}
|
||||
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
if (UdonSharpGUI.DrawConvertToUdonBehaviourButton(target) ||
|
||||
UdonSharpGUI.DrawProgramSource(target))
|
||||
return;
|
||||
|
||||
UdonSharpGUI.DrawUILine();
|
||||
|
||||
EditorGUILayout.LabelField("General", EditorStyles.boldLabel);
|
||||
EditorGUILayout.PropertyField(allowSeekProperty);
|
||||
EditorGUILayout.PropertyField(defaultUnlockedProperty);
|
||||
EditorGUILayout.PropertyField(allowCreatorControlProperty);
|
||||
|
||||
EditorGUILayout.Space();
|
||||
EditorGUILayout.LabelField("Sync", EditorStyles.boldLabel);
|
||||
|
||||
EditorGUILayout.PropertyField(syncFrequencyProperty);
|
||||
EditorGUILayout.PropertyField(syncThresholdProperty);
|
||||
|
||||
EditorGUILayout.Space();
|
||||
EditorGUILayout.LabelField("Audio", EditorStyles.boldLabel);
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
EditorGUI.BeginDisabledGroup(EditorApplication.isPlayingOrWillChangePlaymode);
|
||||
EditorGUILayout.PropertyField(defaultVolumeProperty);
|
||||
EditorGUI.EndDisabledGroup();
|
||||
EditorGUILayout.PropertyField(audioRangeProperty);
|
||||
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
VideoPlayerManager manager = ((Component)target).GetUdonSharpComponentInChildren<VideoPlayerManager>(true);
|
||||
|
||||
foreach (AudioSource source in manager.audioSources)
|
||||
{
|
||||
if (source)
|
||||
{
|
||||
Undo.RecordObject(source, "Change audio properties");
|
||||
source.maxDistance = Mathf.Max(0f, audioRangeProperty.floatValue);
|
||||
source.volume = defaultVolumeProperty.floatValue;
|
||||
|
||||
if (PrefabUtility.IsPartOfPrefabInstance(source))
|
||||
PrefabUtility.RecordPrefabInstancePropertyModifications(source);
|
||||
}
|
||||
}
|
||||
|
||||
VolumeController[] volumeControllers = ((Component)target).GetUdonSharpComponentsInChildren<VolumeController>(true);
|
||||
|
||||
foreach (VolumeController controller in volumeControllers)
|
||||
{
|
||||
if (controller.slider)
|
||||
{
|
||||
Undo.RecordObject(controller.slider, "Change audio properties");
|
||||
controller.slider.value = defaultVolumeProperty.floatValue;
|
||||
|
||||
if (PrefabUtility.IsPartOfPrefabInstance(controller.slider))
|
||||
PrefabUtility.RecordPrefabInstancePropertyModifications(controller.slider);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
EditorGUILayout.Space();
|
||||
EditorGUILayout.LabelField("Playlist", EditorStyles.boldLabel);
|
||||
|
||||
playlistList.DoLayoutList();
|
||||
EditorGUILayout.PropertyField(loopPlaylistProperty);
|
||||
EditorGUILayout.PropertyField(shufflePlaylistProperty);
|
||||
|
||||
EditorGUILayout.Space();
|
||||
EditorGUILayout.LabelField("Stream Settings", EditorStyles.boldLabel);
|
||||
|
||||
EditorGUILayout.PropertyField(defaultStreamMode);
|
||||
|
||||
VRCAVProVideoPlayer avProPlayer = ((Component)target).GetComponentInChildren<VRCAVProVideoPlayer>(true);
|
||||
|
||||
if (avProPlayer)
|
||||
{
|
||||
EditorGUI.BeginChangeCheck();
|
||||
bool newLowLatencyMode = EditorGUILayout.Toggle(new GUIContent("Low Latency Stream", "Whether the stream player should use low latency mode for RTSP streams"), avProPlayer.UseLowLatency);
|
||||
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
//FieldInfo lowLatencyField = typeof(VRCAVProVideoPlayer).GetField("useLowLatency", BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
|
||||
SerializedObject avproPlayerSerializedObject = new SerializedObject(avProPlayer);
|
||||
SerializedProperty lowLatencyField = avproPlayerSerializedObject.FindProperty("useLowLatency");
|
||||
|
||||
lowLatencyField.boolValue = newLowLatencyMode;
|
||||
avproPlayerSerializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
}
|
||||
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ef31bf5c59ccb4f43b38919b302a0d27
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user