public async Task PlayAsync(string deviceUniqueId, string friendlyName) { DevicePlayStateChanged?.Invoke(deviceUniqueId, EState.Playing); CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); if (m_ActivePlayers.ContainsKey(deviceUniqueId) == false) { m_ActivePlayers.Add(deviceUniqueId, cancellationTokenSource); } else { m_ActivePlayers[deviceUniqueId] = cancellationTokenSource; Logger.Error("device {0} already existed in m_ActivePlayers", friendlyName); } try { await _PlayAsync(deviceUniqueId, cancellationTokenSource).ConfigureAwait(false); } catch (NullReferenceException e) { Logger.Error(e, "Error while trying to play, device {0}", friendlyName); Snapcast.Instance.ShowNotification("Device not found", string.Format("Couldn't find sound device '{0}'. Has it been unplugged?", friendlyName)); DevicePlayStateChanged?.Invoke(deviceUniqueId, EState.Stopped); m_ActivePlayers.Remove(deviceUniqueId); if (System.Windows.MessageBox.Show(string.Format("The audio device '{0}' had been marked for auto-play, but is missing from the system. Would you like to remove it from auto-play?", friendlyName), "Snap.Net - Auto-play device missing", System.Windows.MessageBoxButton.YesNo) == System.Windows.MessageBoxResult.Yes) { SnapSettings.SetAudioDeviceAutoPlay(deviceUniqueId, false, friendlyName); } } }
public void Stop(string deviceUniqueId) { if (m_ActivePlayers.ContainsKey(deviceUniqueId)) { m_ActivePlayers[deviceUniqueId].Cancel(); DevicePlayStateChanged?.Invoke(deviceUniqueId, EState.Stopped); m_ActivePlayers.Remove(deviceUniqueId); } }
private async Task _PlayAsync(string deviceUniqueId, CancellationTokenSource cancellationTokenSource, int attempts = 0) { Device device = await FindDeviceAsync(deviceUniqueId); if (device == null) { await Task.FromException(new NullReferenceException("Couldn't get device with uniqueId " + deviceUniqueId)).ConfigureAwait(false); } else { int deviceInstanceId = SnapSettings.DetermineInstanceId(deviceUniqueId, device.Index); DeviceSettings deviceSettings = SnapSettings.GetDeviceSettings(deviceUniqueId); if (deviceInstanceId != -1) { // update device's last seen: deviceSettings.LastSeen = DateTime.Now; SnapSettings.SaveDeviceSettings(deviceUniqueId, deviceSettings); StringBuilder stdError = new StringBuilder(); string lastLine = ""; Action <string> stdOut = (line) => { lastLine = line; // we only care about the last line from the output - in case there's an error (snapclient should probably be sending these to stderr though) }; string resampleArg = ""; string hostIdArg = ""; CommandTask <CommandResult> task = null; if (deviceSettings.UseSnapClientNet == false) { // Launch native client: if (string.IsNullOrEmpty(deviceSettings.ResampleFormat) == false) { resampleArg = $"--sampleformat {deviceSettings.ResampleFormat}:0"; } if (string.IsNullOrWhiteSpace(deviceSettings.HostId) == false) { hostIdArg = $"--hostID \"{deviceSettings.HostId}\""; } string command = $"-h {SnapSettings.Server} -p {SnapSettings.PlayerPort} -s {device.Index} -i {deviceInstanceId} --sharingmode={deviceSettings.ShareMode.ToString().ToLower()} {resampleArg} {hostIdArg}"; Logger.Debug("Snapclient command: {0}", command); task = Cli.Wrap(_SnapClient()) .WithArguments(command) .WithStandardOutputPipe(PipeTarget.ToDelegate(stdOut)) .ExecuteAsync(cancellationTokenSource.Token); } else { // launch experimental .NET port of snapclient: string command = $"-h {SnapSettings.Server} -p {SnapSettings.PlayerPort} -s {device.Index} -i {deviceInstanceId}"; Logger.Debug("SnapClient.Net command: {0}", command); task = Cli.Wrap(_SnapClientDotNet()) .WithArguments(command) .WithStandardOutputPipe(PipeTarget.ToDelegate(stdOut)) .ExecuteAsync(cancellationTokenSource.Token); } Logger.Debug("Snapclient PID: {0}", task.ProcessId); ChildProcessTracker.AddProcess(Process.GetProcessById(task.ProcessId)); // this utility helps us make sure the player process doesn't keep going if our process is killed / crashes try { await task; } catch (CliWrap.Exceptions.CommandExecutionException e) { OnSnapClientErrored?.Invoke(); // add type to ShowNotification (level?), show notification with type and print log at type level Snapcast.Instance.ShowNotification("Snapclient error", _BuildErrorMessage(device, lastLine)); // todo: parse WASAPI error code here and provide human friendly output Logger.Error("Snapclient exited with non-zero exit code. Exception:"); Logger.Error(e.Message); DevicePlayStateChanged?.Invoke(deviceUniqueId, EState.Stopped); m_ActivePlayers.Remove(deviceUniqueId); // settings might have changed while we were playing - refetch them DeviceSettings nDeviceSettings = SnapSettings.GetDeviceSettings(deviceUniqueId); if (nDeviceSettings.AutoRestartOnFailure == true && (attempts <= nDeviceSettings.RestartAttempts || nDeviceSettings.RestartAttempts == 0)) { m_ActivePlayers.Add(deviceUniqueId, cancellationTokenSource); DevicePlayStateChanged?.Invoke(deviceUniqueId, EState.Playing); await _PlayAsync(deviceUniqueId, cancellationTokenSource, attempts + 1).ConfigureAwait(false); } } } } DevicePlayStateChanged?.Invoke(deviceUniqueId, EState.Stopped); m_ActivePlayers.Remove(deviceUniqueId); }
public async Task PlayAsync(string deviceUniqueId, string friendlyName) { DevicePlayStateChanged?.Invoke(deviceUniqueId, EState.Playing); CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); if (m_ActivePlayers.ContainsKey(deviceUniqueId) == false) { m_ActivePlayers.Add(deviceUniqueId, cancellationTokenSource); } else { m_ActivePlayers[deviceUniqueId] = cancellationTokenSource; Logger.Error("device {0} already existed in m_ActivePlayers", friendlyName); } try { await _PlayAsync(deviceUniqueId, cancellationTokenSource).ConfigureAwait(false); } catch (NullReferenceException e) { DevicePlayStateChanged?.Invoke(deviceUniqueId, EState.Stopped); m_ActivePlayers.Remove(deviceUniqueId); Logger.Error(e, "Error while trying to play, device {0}", friendlyName); if (SnapSettings.DeviceMissingBehaviour == SnapSettings.EDeviceMissingBehaviour.Default) { Snapcast.Instance.ShowNotification("Device not found", string.Format("Couldn't find sound device '{0}'. Has it been unplugged?", friendlyName)); } else if (SnapSettings.DeviceMissingRetryIntervalSeconds > 0) { Timer timer = new Timer(SnapSettings.DeviceMissingRetryIntervalSeconds * 1000); timer.Start(); timer.Elapsed += async(sender, args) => { timer.Stop(); await PlayAsync(deviceUniqueId, friendlyName); }; } DeviceSettings settings = SnapSettings.GetDeviceSettings(deviceUniqueId); if (settings.LastSeen == null) // in case we're migrating from a version that didn't have the LastSeen field yet { settings.LastSeen = DateTime.Now; SnapSettings.SaveDeviceSettings(deviceUniqueId, settings); } // if a device hasn't been seen for x days, stop trying to auto-play it if (SnapSettings.DeviceMissingExpiryDays != 0) { if (DateTime.Now - settings.LastSeen > new TimeSpan(SnapSettings.DeviceMissingExpiryDays, 0, 0, 0)) { SnapSettings.SetAudioDeviceAutoPlay(deviceUniqueId, false, friendlyName); } } // this prompt needs rethinking - it's only useful when a device has permanently disappeared // maybe add an extra player list at the bottom with "not found" devices? (disable play button etc) //if (System.Windows.MessageBox.Show(string.Format("The audio device '{0}' had been marked for auto-play, but is missing from the system. Would you like to remove it from auto-play?", friendlyName), // "Snap.Net - Auto-play device missing", System.Windows.MessageBoxButton.YesNo) == System.Windows.MessageBoxResult.Yes) //{ // SnapSettings.SetAudioDeviceAutoPlay(deviceUniqueId, false, friendlyName); //} } }