mixedreality/com.microsoft.mixedreality..../Runtime/FeaturePlugins/PlayModeRemotingPlugin.cs

207 lines
8.4 KiB
C#

// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using System.Globalization;
using System.IO;
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
using UnityEditor.XR.OpenXR.Features;
using UnityEngine.XR.Management;
using UnityEngine.XR.OpenXR;
using UnityEngine.XR.OpenXR.Features;
#endif
namespace Microsoft.MixedReality.OpenXR.Remoting
{
#if UNITY_EDITOR
[OpenXRFeature(UiName = featureName,
BuildTargetGroups = new[] { BuildTargetGroup.Standalone },
Company = "Microsoft",
Desc = featureName + " in Unity editor.",
DocumentationLink = "https://aka.ms/openxr-unity-editor-remoting",
OpenxrExtensionStrings = requestedExtensions,
Category = FeatureCategory.Feature,
Required = false,
Priority = -100, // hookup before other plugins so it affects json before GetProcAddr.
FeatureId = featureId,
Version = "1.11.1")]
#endif
internal class PlayModeRemotingPlugin : OpenXRFeaturePlugin<PlayModeRemotingPlugin>
#if UNITY_EDITOR
, ISerializationCallbackReceiver
#endif
{
internal const string featureId = "com.microsoft.openxr.feature.playmoderemoting";
internal const string featureName = "Holographic Remoting for Play Mode";
private const string requestedExtensions = "XR_MSFT_holographic_remoting XR_MSFT_holographic_remoting_speech";
private const string SettingsFileName = "MixedRealityOpenXRRemotingSettings.asset";
private static string UserSettingsFolder => Path.Combine(Application.dataPath, "..", "UserSettings");
private static string SettingsAssetPath => Path.Combine(UserSettingsFolder, SettingsFileName);
[SerializeField, Tooltip("The host name or IP address of the player running in network server mode to connect to."), Obsolete("Use the remotingSettings values instead")]
private string m_remoteHostName = string.Empty;
[SerializeField, Tooltip("The port number of the server's handshake port."), Obsolete("Use the remotingSettings values instead")]
private ushort m_remoteHostPort = 8265;
[SerializeField, Tooltip("The max bitrate in Kbps to use for the connection."), Obsolete("Use the remotingSettings values instead")]
private uint m_maxBitrate = 20000;
[SerializeField, Tooltip("The video codec to use for the connection."), Obsolete("Use the remotingSettings values instead")]
private RemotingVideoCodec m_videoCodec = RemotingVideoCodec.Auto;
[SerializeField, Tooltip("Enable/disable audio remoting."), Obsolete("Use the remotingSettings values instead")]
private bool m_enableAudio = false;
private readonly bool m_playModeRemotingIsActive =
#if UNITY_EDITOR
true;
#else
false;
#endif
private RemotingSettings m_remotingSettings;
protected override IntPtr HookGetInstanceProcAddr(IntPtr func)
{
if (enabled && m_playModeRemotingIsActive)
{
AppRemotingSubsystem.GetCurrent().TryEnableRemotingOverride();
}
return base.HookGetInstanceProcAddr(func);
}
protected override void OnInstanceDestroy(ulong instance)
{
if (enabled && m_playModeRemotingIsActive)
{
AppRemotingSubsystem.GetCurrent().ResetRemotingOverride();
}
base.OnInstanceDestroy(instance);
}
protected override void OnSystemChange(ulong systemId)
{
base.OnSystemChange(systemId);
if (systemId != 0 && m_playModeRemotingIsActive)
{
RemotingSettings remotingSettings = GetOrLoadRemotingSettings();
AppRemotingSubsystem.GetCurrent().InitializePlayModeRemoting(new RemotingConnectConfiguration
{
RemoteHostName = remotingSettings.RemoteHostName,
RemotePort = remotingSettings.RemoteHostPort,
MaxBitrateKbps = remotingSettings.MaxBitrate,
VideoCodec = remotingSettings.VideoCodec,
EnableAudio = remotingSettings.EnableAudio,
AudioCaptureMode = remotingSettings.AudioCaptureMode,
secureConnectConfiguration = null
});
}
}
protected override void OnSessionStateChange(int oldState, int newState)
{
if (m_playModeRemotingIsActive && (XrSessionState)newState == XrSessionState.LossPending)
{
AppRemotingSubsystem.GetCurrent().OnSessionLossPending();
#if UNITY_EDITOR
EditorApplication.ExitPlaymode();
#endif
}
}
internal RemotingSettings GetOrLoadRemotingSettings()
{
if (m_remotingSettings == null)
{
// If this file doesn't yet exist, create it and port from the old values.
m_remotingSettings = CreateInstance<RemotingSettings>();
if (File.Exists(SettingsAssetPath))
{
using (StreamReader settingsReader = new StreamReader(SettingsAssetPath))
{
JsonUtility.FromJsonOverwrite(settingsReader.ReadToEnd(), m_remotingSettings);
}
}
else
{
#pragma warning disable CS0618 // to use the obsolete fields to port to the new asset file
m_remotingSettings.RemoteHostName = m_remoteHostName;
m_remotingSettings.RemoteHostPort = m_remoteHostPort;
m_remotingSettings.MaxBitrate = m_maxBitrate;
m_remotingSettings.VideoCodec = m_videoCodec;
m_remotingSettings.EnableAudio = m_enableAudio;
#pragma warning restore CS0618
}
}
return m_remotingSettings;
}
#if UNITY_EDITOR
internal bool HasValidSettings() => !string.IsNullOrEmpty(GetOrLoadRemotingSettings().RemoteHostName);
private void SaveSettings()
{
// Don't try to load the settings here. If this is null, then there's
// no need to do extra work to load and save the same file.
// When remoting is used, this is guaranteed to be non-null.
if (m_remotingSettings == null)
{
return;
}
if (!Directory.Exists(UserSettingsFolder))
{
Directory.CreateDirectory(UserSettingsFolder);
}
using (StreamWriter settingsWriter = new StreamWriter(SettingsAssetPath))
{
settingsWriter.Write(JsonUtility.ToJson(m_remotingSettings, true));
}
}
protected override void GetValidationChecks(System.Collections.Generic.List<ValidationRule> results, BuildTargetGroup targetGroup)
{
PlayModeRemotingValidator.GetValidationChecks(this, results);
}
void ISerializationCallbackReceiver.OnBeforeSerialize() => SaveSettings();
void ISerializationCallbackReceiver.OnAfterDeserialize() { } // Can't call EnsureSettingsLoaded() here, since Application.dataPath can't be accessed during deserialization
[InitializeOnEnterPlayMode]
private static void OnEnterPlaymodeInEditor(EnterPlayModeOptions options)
{
StartOrStopXRHelper.OnEnterPlaymodeInEditor();
}
#endif
}
internal class RemotingSettings : ScriptableObject
{
[field: SerializeField, Tooltip("The host name or IP address of the player running in network server mode to connect to.")]
public string RemoteHostName { get; set; } = string.Empty;
[field: SerializeField, Tooltip("The port number of the server's handshake port.")]
public ushort RemoteHostPort { get; set; } = 8265;
[field: SerializeField, Tooltip("The max bitrate in Kbps to use for the connection.")]
public uint MaxBitrate { get; set; } = 20000;
[field: SerializeField, Tooltip("The video codec to use for the connection.")]
public RemotingVideoCodec VideoCodec { get; set; } = RemotingVideoCodec.Auto;
[field: SerializeField, Tooltip("Enable/disable audio remoting.")]
public bool EnableAudio { get; set; } = false;
[field: SerializeField, Tooltip("The audio capture mode to use for the connection.")]
public RemotingAudioCaptureMode AudioCaptureMode {get; set;} = RemotingAudioCaptureMode.SystemWideCapture;
}
}