/// <summary> /// Create start HTTP request /// </summary> private XmlQuery StartOrResumeApp(NvHttp nv, LimelightStreamConfiguration streamConfig) { XmlQuery serverInfo = new XmlQuery(nv.baseUrl + "/serverinfo?uniqueid=" + nv.GetUniqueId()); string currentGameString = serverInfo.XmlAttribute("currentgame"); byte[] aesIv = streamConfig.GetRiAesIv(); int riKeyId = (int)(((aesIv[0] << 24) & 0xFF000000) | ((aesIv[1] << 16) & 0xFF0000) | ((aesIv[2] << 8) & 0xFF00) | (aesIv[3] & 0xFF)); string riConfigString = "&rikey=" + Pairing.bytesToHex(streamConfig.GetRiAesKey()) + "&rikeyid=" + riKeyId; // Launch a new game if nothing is running if (currentGameString == null || currentGameString.Equals("0")) { return new XmlQuery(nv.baseUrl + "/launch?uniqueid=" + nv.GetUniqueId() + "&appid=" + selected.steamID + "&mode=" + streamConfig.GetWidth() + "x" + streamConfig.GetHeight() + "x" + streamConfig.GetFps() + "&additionalStates=1&sops=1" + // FIXME: make sops configurable riConfigString); } else { // A game was already running, so resume it // FIXME: Quit and relaunch if it's not the game we came to start return new XmlQuery(nv.baseUrl + "/resume?uniqueid=" + nv.GetUniqueId() + riConfigString); } }
/// <summary> /// Create start HTTP request /// </summary> private XmlQuery StartOrResumeApp(NvHttp nv, LimelightStreamConfiguration streamConfig) { XmlQuery serverInfo = new XmlQuery(nv.BaseUrl + "/serverinfo?uniqueid=" + nv.GetUniqueId()); string currentGameString = serverInfo.XmlAttribute("currentgame"); byte[] aesIv = streamConfig.GetRiAesIv(); int riKeyId = (int)(((aesIv[0] << 24) & 0xFF000000) | ((aesIv[1] << 16) & 0xFF0000) | ((aesIv[2] << 8) & 0xFF00) | (aesIv[3] & 0xFF)); string riConfigString = "&rikey=" + Pairing.bytesToHex(streamConfig.GetRiAesKey()) + "&rikeyid=" + riKeyId; // Launch a new game if nothing is running if (currentGameString == null || currentGameString.Equals("0")) { return(new XmlQuery(nv.BaseUrl + "/launch?uniqueid=" + nv.GetUniqueId() + "&appid=" + selected.steamID + "&mode=" + streamConfig.GetWidth() + "x" + streamConfig.GetHeight() + "x" + streamConfig.GetFps() + "&additionalStates=1&sops=1" + // FIXME: make sops configurable riConfigString)); } else { // A game was already running, so resume it // FIXME: Quit and relaunch if it's not the game we came to start return(new XmlQuery(nv.BaseUrl + "/resume?uniqueid=" + nv.GetUniqueId() + riConfigString)); } }
/// <summary> /// Event handler for page loaded event /// </summary> private async void Loaded(object sender, RoutedEventArgs e) { StreamDisplay.Visibility = Visibility.Visible; Waitgrid.Visibility = Visibility.Collapsed; currentStateText.Visibility = Visibility.Collapsed; // Hide the status bar //var statusBar = Windows.UI.ViewManagement.StatusBar.GetForCurrentView(); //await statusBar.HideAsync(); LimelightStreamConfiguration config; byte[] aesKey = Pairing.GenerateRandomBytes(16); // GameStream only uses 4 bytes of a 16 byte IV. Go figure. byte[] aesRiIndex = Pairing.GenerateRandomBytes(4); byte[] aesIv = new byte[16]; Array.ConstrainedCopy(aesRiIndex, 0, aesIv, 0, aesRiIndex.Length); config = new LimelightStreamConfiguration(selected.pixels * (16 / 9), selected.pixels, selected.fps, 5000, 1024, aesKey, aesIv); InitializeMediaPlayer(config, AvStream); //H264FileReaderHackery h = new H264FileReaderHackery(); //Task.Run(() => h.readFile(this)); await StartConnection(config); }
/// <summary> /// Starts the connection by calling into Limelight Common /// </summary> private async Task StartConnection() { NvHttp nv = null; await SetStateText("Resolving hostname..."); try { nv = new NvHttp(selected.IpAddress); } catch (ArgumentNullException) { stageFailureText = "Error resolving hostname"; ConnectionFailed(); } XmlQuery launchApp; // Launch Steam await SetStateText("Launching Steam"); try { launchApp = new XmlQuery(nv.baseUrl + "/launch?uniqueid=" + nv.GetDeviceName() + "&appid=" + selected.steamID); } catch (Exception) { Debug.WriteLine("Can't find steam"); stageFailureText = "Error launching Steam"; ConnectionFailed(); return; } // Set up callbacks LimelightStreamConfiguration streamConfig = new LimelightStreamConfiguration(frameWidth, frameHeight, 30, 10000, 1024); // TODO a magic number. Get FPS from the settings LimelightDecoderRenderer drCallbacks = new LimelightDecoderRenderer(DrSetup, DrStart, DrStop, DrRelease, DrSubmitDecodeUnit); LimelightAudioRenderer arCallbacks = new LimelightAudioRenderer(ArInit, ArStart, ArStop, ArRelease, ArPlaySample); LimelightConnectionListener clCallbacks = new LimelightConnectionListener(ClStageStarting, ClStageComplete, ClStageFailed, ClConnectionStarted, ClConnectionTerminated, ClDisplayMessage, ClDisplayTransientMessage); // Call into Common to start the connection Debug.WriteLine("Starting connection"); uint addr = 0; //uint addr = (uint)nv.resolvedHost.ToString(); // TODO how to get the addr as a uint LimelightCommonRuntimeComponent.StartConnection(addr, streamConfig, clCallbacks, drCallbacks, arCallbacks); if (stageFailureText != null) { Debug.WriteLine("Stage failed"); ConnectionFailed(); return; } else { ConnectionSuccess(); } }
/// <summary> /// Initialize the media element for playback /// </summary> /// <param name="streamConfig">Object containing stream configuration details</param> void InitializeMediaPlayer(LimelightStreamConfiguration streamConfig, AvStreamSource streamSource) { this._streamSource = streamSource; AudioEncodingProperties audioProperties = AudioEncodingProperties.CreatePcm(48000, 2, 16); VideoEncodingProperties videoProperties = VideoEncodingProperties.CreateUncompressed(MediaEncodingSubtypes.H264Es, (uint)streamConfig.GetWidth(), (uint)streamConfig.GetHeight()); videoProperties.ProfileId = H264ProfileIds.High; _videoDesc = new VideoStreamDescriptor(videoProperties); _audioDesc = new AudioStreamDescriptor(audioProperties); _mss = new MediaStreamSource(_videoDesc, _audioDesc); _mss.BufferTime = TimeSpan.Zero; _mss.CanSeek = false; _mss.Duration = TimeSpan.Zero; _mss.SampleRequested += _mss_SampleRequested; // Set for low latency playback StreamDisplay.RealTimePlayback = true; // Set the audio category to take advantage of hardware audio offload StreamDisplay.AudioCategory = AudioCategory.ForegroundOnlyMedia; // 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(_mss); }
/// <summary> /// Starts the connection by calling into Limelight Common /// </summary> private async Task StartConnection(LimelightStreamConfiguration streamConfig) { NvHttp nv = null; await SetStateText("Resolving hostname..."); try { nv = new NvHttp(selected.IpAddress); } catch (ArgumentNullException) { stageFailureText = "Error resolving hostname"; ConnectionFailed(); return; } try { await nv.ServerIPAddress(); } catch (Exception) { stageFailureText = "Error resolving hostname"; ConnectionFailed(); return; } // Set up callbacks LimelightDecoderRenderer drCallbacks = new LimelightDecoderRenderer(DrSetup, DrStart, DrStop, DrRelease, DrSubmitDecodeUnit); LimelightAudioRenderer arCallbacks = new LimelightAudioRenderer(ArInit, ArStart, ArStop, ArRelease, ArPlaySample); LimelightConnectionListener clCallbacks = new LimelightConnectionListener(ClStageStarting, ClStageComplete, ClStageFailed, ClConnectionStarted, ClConnectionTerminated, ClDisplayMessage, ClDisplayTransientMessage); XmlQuery launchApp; // Launch Steam await SetStateText("Launching Steam"); try { launchApp = StartOrResumeApp(nv, streamConfig); } catch (Exception) { Debug.WriteLine("Can't find steam"); stageFailureText = "Error launching Steam"; ConnectionFailed(); return; } // Call into Common to start the connection Debug.WriteLine("Starting connection"); Regex r = new Regex(@"^(?<octet1>\d+).(?<octet2>\d+).(?<octet3>\d+).(?<octet4>\d+)"); Match m = r.Match(selected.IpAddress); uint addr = (uint)(Convert.ToByte(m.Groups["octet4"].Value) << 24 | Convert.ToByte(m.Groups["octet3"].Value) << 16 | Convert.ToByte(m.Groups["octet2"].Value) << 8 | Convert.ToByte(m.Groups["octet1"].Value)); LimelightCommonRuntimeComponent.StartConnection(addr, streamConfig, clCallbacks, drCallbacks, arCallbacks); if (stageFailureText != null) { Debug.WriteLine("Stage failed"); ConnectionFailed(); return; } else { ConnectionSuccess(); } }
/// <summary> /// Event handler for Background Worker's doWork event. /// </summary> private void bwDoWork(object sender, DoWorkEventArgs e) { Debug.WriteLine("Doing work"); String hostNameString = (String)PhoneApplicationService.Current.State["host"]; // Resolve the host name to an IP address string. Dispatcher.BeginInvoke(new Action(() => setStateText("Resolving hostname..."))); ResolveHostName(hostNameString); stopWaitHandle.WaitOne(); // Set up callbacks LimelightStreamConfiguration streamConfig = new LimelightStreamConfiguration(frameWidth, frameHeight, 30); // TODO a magic number. Get FPS from the settings LimelightDecoderRenderer drCallbacks = new LimelightDecoderRenderer(DrSetup, DrStart, DrStop, DrRelease, DrSubmitDecodeUnit); LimelightAudioRenderer arCallbacks = new LimelightAudioRenderer(ArInit, ArStart, ArStop, ArRelease, ArPlaySample); LimelightConnectionListener clCallbacks = new LimelightConnectionListener(ClStageStarting, ClStageComplete, ClStageFailed, ClConnectionStarted, ClConnectionTerminated, ClDisplayMessage, ClDisplayTransientMessage); // Call into Common to start the connection Debug.WriteLine("Starting connection"); LimelightCommonRuntimeComponent.StartConnection((uint)resolvedHost.Address, streamConfig, clCallbacks, drCallbacks, arCallbacks); // If one of the stages failed, tell the background worker to cancel if(stageFailureText != null) { Debug.WriteLine("Stage failed - background worker cancelled"); e.Cancel = true; } }
/// <summary> /// Event handler for page loaded event /// </summary> private async void Loaded(object sender, RoutedEventArgs e) { StreamDisplay.Visibility = Visibility.Visible; Waitgrid.Visibility = Visibility.Collapsed; currentStateText.Visibility = Visibility.Collapsed; // Hide the status bar //var statusBar = Windows.UI.ViewManagement.StatusBar.GetForCurrentView(); //await statusBar.HideAsync(); LimelightStreamConfiguration config; byte[] aesKey = Pairing.GenerateRandomBytes(16); // GameStream only uses 4 bytes of a 16 byte IV. Go figure. byte[] aesRiIndex = Pairing.GenerateRandomBytes(4); byte[] aesIv = new byte[16]; Array.ConstrainedCopy(aesRiIndex, 0, aesIv, 0, aesRiIndex.Length); config = new LimelightStreamConfiguration(selected.pixels*(16/9), selected.pixels, selected.fps, 5000, 1024, aesKey, aesIv); InitializeMediaPlayer(config, AvStream); //H264FileReaderHackery h = new H264FileReaderHackery(); //Task.Run(() => h.readFile(this)); await StartConnection(config); }