/// <summary> /// Initialize the media element for playback /// </summary> /// <param name="streamConfig">Object containing stream configuration details</param> void InitializeMediaPlayer(MoonlightStreamConfiguration streamConfig, AvStreamSource streamSource) { this._streamSource = streamSource; // This code is based upon the MS FFmpegInterop project on GitHub VideoEncodingProperties videoProps = VideoEncodingProperties.CreateH264(); videoProps.ProfileId = H264ProfileIds.High; videoProps.Width = (uint)streamConfig.GetWidth(); videoProps.Height = (uint)streamConfig.GetHeight(); videoProps.Bitrate = (uint)streamConfig.GetBitrate(); _videoMss = new MediaStreamSource(new VideoStreamDescriptor(videoProps)); _videoMss.BufferTime = TimeSpan.Zero; _videoMss.CanSeek = false; _videoMss.Duration = TimeSpan.Zero; _videoMss.SampleRequested += _videoMss_SampleRequested; XAudio2 xaudio = new XAudio2(); MasteringVoice masteringVoice = new MasteringVoice(xaudio, 2, 48000); WaveFormat format = new WaveFormat(48000, 16, 2); // Set for low latency playback StreamDisplay.RealTimePlayback = true; // Render on the full window to avoid extra compositing StreamDisplay.IsFullWindow = true; // Disable built-in transport controls StreamDisplay.AreTransportControlsEnabled = false; StreamDisplay.SetMediaStreamSource(_videoMss); AvStream.SetSourceVoice(new SourceVoice(xaudio, format)); }
/// <summary> /// When the user presses "Start Streaming Steam", first check that they are paired /// </summary> public static async Task<StreamContext> StartStreaming(CoreDispatcher uiDispatcher, Computer computer, MoonlightStreamConfiguration streamConfig) { PairingManager p = new PairingManager(computer); // If we can't get the pair state, return bool? pairState = await p.QueryPairState(); if (!pairState.HasValue) { DialogUtils.DisplayDialog(uiDispatcher, "Pair state query failed", "Failed to start streaming"); return null; } // If we're not paired, return if (pairState == false) { DialogUtils.DisplayDialog(uiDispatcher, "Device not paired", "Failed to start streaming"); return null; } // Lookup the desired app in the app list // NOTE: This will go away when we have a proper app list int appId = await LookupAppIdForApp(uiDispatcher, new NvHttp(computer.IpAddress), "Steam"); if (appId == 0) { // LookupAppIdForApp() handles displaying a failure dialog return null; } return new StreamContext(computer, appId, streamConfig); }
/// <summary> /// Initialize the media element for playback /// </summary> /// <param name="streamConfig">Object containing stream configuration details</param> void InitializeMediaPlayer(MoonlightStreamConfiguration streamConfig, AvStreamSource streamSource) { this._streamSource = streamSource; _videoMss = new MediaStreamSource(new VideoStreamDescriptor(VideoEncodingProperties.CreateH264())); _videoMss.BufferTime = TimeSpan.Zero; _videoMss.CanSeek = false; _videoMss.Duration = TimeSpan.Zero; _videoMss.SampleRequested += _videoMss_SampleRequested; XAudio2 xaudio = new XAudio2(); MasteringVoice masteringVoice = new MasteringVoice(xaudio, 2, 48000); WaveFormat format = new WaveFormat(48000, 16, 2); // Set for low latency playback StreamDisplay.RealTimePlayback = true; // Render on the full window to avoid extra compositing StreamDisplay.IsFullWindow = true; // Disable built-in transport controls StreamDisplay.AreTransportControlsEnabled = false; // Start playing right away StreamDisplay.AutoPlay = true; StreamDisplay.SetMediaStreamSource(_videoMss); AvStream.SetSourceVoice(new SourceVoice(xaudio, format)); }
/// <summary> /// Starts the connection by calling into Moonlight Common /// </summary> private async Task StartConnection(MoonlightStreamConfiguration streamConfig) { NvHttp nv = null; await SetStateText("Resolving hostname..."); try { nv = new NvHttp(context.computer.IpAddress); } catch (ArgumentNullException) { stageFailureText = "Error resolving hostname"; ConnectionFailed(); return; } String serverIp = null; try { serverIp = await nv.ResolveServerIPAddress(); } catch (Exception) { stageFailureText = "Error resolving hostname"; ConnectionFailed(); return; } // Set up callbacks MoonlightDecoderRenderer drCallbacks = new MoonlightDecoderRenderer(DrSetup, DrCleanup, DrSubmitDecodeUnit); MoonlightAudioRenderer arCallbacks = new MoonlightAudioRenderer(ArInit, ArCleanup, ArPlaySample); MoonlightConnectionListener clCallbacks = new MoonlightConnectionListener(ClStageStarting, ClStageComplete, ClStageFailed, ClConnectionStarted, ClConnectionTerminated, ClDisplayMessage, ClDisplayTransientMessage); // Launch Steam await SetStateText("Launching Steam"); if (await StartOrResumeApp(nv, streamConfig) == false) { Debug.WriteLine("Can't find app"); stageFailureText = "Error launching App"; ConnectionFailed(); return; } // Call into Common to start the connection Debug.WriteLine("Starting connection"); MoonlightCommonRuntimeComponent.StartConnection(serverIp, streamConfig, clCallbacks, drCallbacks, arCallbacks, serverMajorVersion); if (stageFailureText != null) { Debug.WriteLine("Stage failed"); ConnectionFailed(); return; } else { ConnectionSuccess(); } }
/// <summary> /// Create start HTTP request /// </summary> private async Task<bool> StartOrResumeApp(NvHttp nv, MoonlightStreamConfiguration streamConfig) { XmlQuery serverInfo = new XmlQuery(nv.BaseUrl + "/serverinfo?uniqueid=" + nv.GetUniqueId()); string currentGameString = await serverInfo.ReadXmlElement("currentgame"); if (currentGameString == null) { return false; } string versionString = await serverInfo.ReadXmlElement("appversion"); if (versionString == null) { return false; } serverMajorVersion = Convert.ToInt32(versionString.Substring(0, 1)); byte[] aesIv = streamConfig.GetRiAesIv(); int riKeyId = (int)(((aesIv[0] << 24) & 0xFF000000U) | ((aesIv[1] << 16) & 0xFF0000U) | ((aesIv[2] << 8) & 0xFF00U) | (aesIv[3] & 0xFFU)); string riConfigString = "&rikey=" + PairingCryptoHelpers.BytesToHex(streamConfig.GetRiAesKey()) + "&rikeyid=" + riKeyId; // Launch a new game if nothing is running if (currentGameString == null || currentGameString.Equals("0")) { XmlQuery x = new XmlQuery(nv.BaseUrl + "/launch?uniqueid=" + nv.GetUniqueId() + "&appid=" + context.appId + "&mode=" + streamConfig.GetWidth() + "x" + streamConfig.GetHeight() + "x" + streamConfig.GetFps() + "&additionalStates=1&sops=1" + // FIXME: make sops configurable riConfigString); string sessionStr = await x.ReadXmlElement("gamesession"); if (sessionStr == null || sessionStr.Equals("0")) { return false; } return true; } else { // A game was already running, so resume it // FIXME: Quit and relaunch if it's not the game we came to start XmlQuery x = new XmlQuery(nv.BaseUrl + "/resume?uniqueid=" + nv.GetUniqueId() + riConfigString); string resumeStr = await x.ReadXmlElement("resume"); if (resumeStr == null || resumeStr.Equals("0")) { return false; } return true; } }
/// <summary> /// Executed when the user presses "Start Streaming Steam!" /// </summary> private async Task StreamButton_Click_Common() { Debug.WriteLine("Start Streaming button pressed"); selected = (Computer)computerPicker.SelectedItem; // User hasn't selected a machine or selected a placeholder if (selected == null || String.IsNullOrWhiteSpace(selected.IpAddress)) { DialogUtils.DisplayDialog(this.Dispatcher, "No machine selected", "Streaming Failed"); } else { // Stop enumerating machines while we're trying to check pair state mDnsTimer.Stop(); byte[] aesKey = PairingCryptoHelpers.GenerateRandomBytes(16); // GameStream only uses 4 bytes of a 16 byte IV. Go figure. byte[] aesRiIndex = PairingCryptoHelpers.GenerateRandomBytes(4); byte[] aesIv = new byte[16]; Array.ConstrainedCopy(aesRiIndex, 0, aesIv, 0, aesRiIndex.Length); SettingsPage s = new SettingsPage(); MoonlightStreamConfiguration config = new MoonlightStreamConfiguration( s.GetStreamWidth(), s.GetStreamHeight(), s.GetStreamFps(), 10000, // FIXME: Scale by resolution 1024, aesKey, aesIv); StreamContext context = await ConnectionManager.StartStreaming(this.Dispatcher, selected, config); if (context != null) { this.Frame.Navigate(typeof(StreamFrame), context); } } }
/// <summary> /// When the user presses "Start Streaming Steam", first check that they are paired /// </summary> public static async Task <StreamContext> StartStreaming(CoreDispatcher uiDispatcher, Computer computer, MoonlightStreamConfiguration streamConfig) { PairingManager p = new PairingManager(computer); // If we can't get the pair state, return bool?pairState = await p.QueryPairState(); if (!pairState.HasValue) { DialogUtils.DisplayDialog(uiDispatcher, "Pair state query failed", "Failed to start streaming"); return(null); } // If we're not paired, return if (pairState == false) { DialogUtils.DisplayDialog(uiDispatcher, "Device not paired", "Failed to start streaming"); return(null); } // Lookup the desired app in the app list // NOTE: This will go away when we have a proper app list int appId = await LookupAppIdForApp(uiDispatcher, new NvHttp(computer.IpAddress), "Steam"); if (appId == 0) { // LookupAppIdForApp() handles displaying a failure dialog return(null); } return(new StreamContext(computer, appId, streamConfig)); }
public StreamContext(Computer computer, int appId, MoonlightStreamConfiguration streamConfig) { this.computer = computer; this.appId = appId; this.streamConfig = streamConfig; }
/// <summary> /// Create start HTTP request /// </summary> private async Task <bool> StartOrResumeApp(NvHttp nv, MoonlightStreamConfiguration streamConfig) { XmlQuery serverInfo = new XmlQuery(nv.BaseUrl + "/serverinfo?uniqueid=" + nv.GetUniqueId()); string currentGameString = await serverInfo.ReadXmlElement("currentgame"); if (currentGameString == null) { return(false); } string versionString = await serverInfo.ReadXmlElement("appversion"); if (versionString == null) { return(false); } serverMajorVersion = Convert.ToInt32(versionString.Substring(0, 1)); byte[] aesIv = streamConfig.GetRiAesIv(); int riKeyId = (int)(((aesIv[0] << 24) & 0xFF000000U) | ((aesIv[1] << 16) & 0xFF0000U) | ((aesIv[2] << 8) & 0xFF00U) | (aesIv[3] & 0xFFU)); string riConfigString = "&rikey=" + PairingCryptoHelpers.BytesToHex(streamConfig.GetRiAesKey()) + "&rikeyid=" + riKeyId; // Launch a new game if nothing is running if (currentGameString == null || currentGameString.Equals("0")) { XmlQuery x = new XmlQuery(nv.BaseUrl + "/launch?uniqueid=" + nv.GetUniqueId() + "&appid=" + context.appId + "&mode=" + streamConfig.GetWidth() + "x" + streamConfig.GetHeight() + "x" + streamConfig.GetFps() + "&additionalStates=1&sops=1" + // FIXME: make sops configurable riConfigString); string sessionStr = await x.ReadXmlElement("gamesession"); if (sessionStr == null || sessionStr.Equals("0")) { return(false); } return(true); } else { // A game was already running, so resume it // FIXME: Quit and relaunch if it's not the game we came to start XmlQuery x = new XmlQuery(nv.BaseUrl + "/resume?uniqueid=" + nv.GetUniqueId() + riConfigString); string resumeStr = await x.ReadXmlElement("resume"); if (resumeStr == null || resumeStr.Equals("0")) { return(false); } return(true); } }