public SdlProducer(
            NanoClient client,
            AudioFormat audioFormat,
            VideoFormat videoFormat,
            bool fullscreen    = false,
            bool useController = true
            )
        {
            _cancellationTokenSource = new CancellationTokenSource();
            _client = client;

            _audioRenderer = new SdlAudio((int)audioFormat.SampleRate, (int)audioFormat.Channels);
            _videoRenderer = new SdlVideo((int)videoFormat.Width, (int)videoFormat.Height, fullscreen);

            Decoder = new FFmpegDecoder(client, audioFormat, videoFormat);

            string baseDir = AppDomain.CurrentDomain.BaseDirectory;

            _useController = useController;

            if (_useController)
            {
                Input             = new SdlInput($"{baseDir}/gamecontrollerdb.txt");
                HandleInputEvent += Input.HandleInput;
            }
        }
        public static async Task <NanoClient> InitNano(string ipAddress, GamestreamSession session)
        {
            NanoClient nano = new NanoClient(ipAddress, session);

            try
            {
                // General Handshaking & Opening channels
                await nano.InitializeProtocolAsync();

                await nano.OpenInputChannelAsync(1280, 720);

                // Audio & Video client handshaking
                // Sets desired AV formats
                _audioFormat = nano.AudioFormats[0];
                _videoFormat = nano.VideoFormats[0];

                await nano.InitializeStreamAsync(_audioFormat, _videoFormat);

                // TODO: Send opus audio chat samples to console
                _chatAudioFormat = new AudioFormat(1, 24000, AudioCodec.Opus);
                await nano.OpenChatAudioChannelAsync(_chatAudioFormat);

                // Tell console to start sending AV frames
                await nano.StartStreamAsync();
            }
            catch (Exception e)
            {
                Console.WriteLine($"Failed to init Nano, error: {e}");
                return(null);
            }

            return(nano);
        }
Exemple #3
0
        public FFmpegDecoder(NanoClient nano, AudioFormat audioFormat, VideoFormat videoFormat)
        {
            _nano        = nano;
            _audioFormat = audioFormat;
            _videoFormat = videoFormat;

            _audioAssembler = new AudioAssembler();
            _videoAssembler = new VideoAssembler();

            _audioRefTimestamp = _nano.Audio.ReferenceTimestamp;
            _videoRefTimestamp = _nano.Video.ReferenceTimestamp;

            _audioFrameId = _nano.Audio.FrameId;
            _videoFrameId = _nano.Video.FrameId;

            _audioHandler = new FFmpegAudio();
            _videoHandler = new FFmpegVideo();

            _audioHandler.Initialize(_audioFormat);
            _videoHandler.Initialize(_videoFormat);
            _audioHandler.CreateDecoderContext();
            _videoHandler.CreateDecoderContext();

            DecodedAudioQueue = new Queue <PCMSample>();
            DecodedVideoQueue = new Queue <YUVFrame>();

            // Register queues for decoded video frames / audio samples
            _audioHandler.SampleDecoded += DecodedAudioQueue.Enqueue;
            _videoHandler.FrameDecoded  += DecodedVideoQueue.Enqueue;
        }
Exemple #4
0
 public StreamingChannelBase(NanoClient client, NanoChannelId channelId)
 {
     _client        = client;
     ChannelId      = channelId;
     ChannelNumber  = 0;
     SequenceNumber = 0;
     IsOpen         = false;
 }
 public ChannelManager(NanoClient client)
 {
     _client       = client;
     _channels     = new Dictionary <ushort, NanoChannelId>();
     Audio         = new AudioChannel(_client);
     ChatAudio     = new ChatAudioChannel(_client);
     Control       = new ControlChannel(_client);
     Input         = new InputChannel(_client);
     InputFeedback = new InputFeedbackChannel(_client);
     Video         = new VideoChannel(_client);
 }
Exemple #6
0
 void FixedUpdate()
 {
     if (myClient != null)
     {
         myClient.doUpdate();
     }
     //联机成功布置棋盘一次
     if (NanoClient.isConnected() && !bChessPos)
     {
         bChessPos = true;
         m_bboard.chessposition();
     }
 }
        /*
         * Protocol
         */

        public async Task StartStream(SurfaceTexture surface)
        {
            System.Diagnostics.Debug.WriteLine($"Connecting to console...");

            _smartGlassClient = await SmartGlassClient.ConnectAsync(_hostName);

            // Get general gamestream configuration
            var config = GamestreamConfiguration.GetStandardConfig();

            /* Modify standard config, if desired */

            var broadcastChannel = _smartGlassClient.BroadcastChannel;
            var session          = await broadcastChannel.StartGamestreamAsync(config);

            System.Diagnostics.Debug.WriteLine(
                $"Connecting to Nano, TCP: {session.TcpPort}, UDP: {session.UdpPort}");

            _nanoClient = new NanoClient(_hostName, session);

            // General Handshaking & Opening channels
            await _nanoClient.InitializeProtocolAsync();

            // Audio & Video client handshaking
            // Sets desired AV formats
            Packets.AudioFormat audioFormat = _nanoClient.AudioFormats[0];
            Packets.VideoFormat videoFormat = _nanoClient.VideoFormats[0];
            await _nanoClient.InitializeStreamAsync(audioFormat, videoFormat);

            // Start ChatAudio channel
            Packets.AudioFormat chatAudioFormat = new Packets.AudioFormat(1, 24000, AudioCodec.Opus);
            await _nanoClient.OpenChatAudioChannelAsync(chatAudioFormat);

            _mcConsumer = new MediaCoreConsumer(surface, audioFormat, videoFormat);
            _nanoClient.AddConsumer(_mcConsumer);

            // Tell console to start sending AV frames
            await _nanoClient.StartStreamAsync();

            // Start Controller input channel
            await _nanoClient.OpenInputChannelAsync(1280, 720);

            System.Diagnostics.Debug.WriteLine($"Nano connected and running.");
        }
Exemple #8
0
        public override async Task <CommandResult> ExecuteAsync(CancellationToken cancel)
        {
            Console.WriteLine($"Connecting to {Hostname}...");

            try
            {
                using (Client = await SmartGlassClient.ConnectAsync(Hostname))
                {
                    var broadcastChannel = Client.BroadcastChannel;
                    // TODO: Wait for BroadcastMessages here...

                    var result = await broadcastChannel.StartGamestreamAsync();

                    Console.WriteLine($"Connecting to Nano, TCP: {result.TcpPort}, UDP: {result.UdpPort}");
                    var nano = new NanoClient(Hostname, result.TcpPort, result.UdpPort, new Guid());
                    await nano.Initialize();

                    FileConsumer consumer = new FileConsumer("nanostream");
                    nano.AddConsumer(consumer);

                    await nano.StartStream();

                    var loop = new Loop(typeof(SessionCommandType));
                    loop.Execute();

                    Console.WriteLine($"Disconnected");
                }

                return(CommandResult.Success);
            }
            catch (Exception e)
            {
                Console.WriteLine($"Failed to connect: {e}");
            }
            finally
            {
                Client = null;
            }

            return(CommandResult.RuntimeFailure);
        }
Exemple #9
0
 public ControlChannel(NanoClient client)
     : base(client, NanoChannelId.Control)
 {
 }
        public NanoTwitchBotCore(NanoTwitchBotStorage storage, string twitchAuthToken)
        {
            // Preconditions
            if (storage == null)
            {
                throw new ArgumentNullException(nameof(storage));
            }

            if (twitchAuthToken == null || string.IsNullOrWhiteSpace(twitchAuthToken))
            {
                throw new ArgumentNullException(nameof(twitchAuthToken));
            }

            // Storage
            this.Storage = storage;

            // Messages
            this.messages = new List <NanoTwitchBotMessage>();

            // Nano
            NanoClient nanoClient = new NanoClient();

            this.Listener = new NanoListener(nanoClient, this.Storage.NanoAccount, TimeSpan.FromSeconds(this.Storage.PollingIntervalInSeconds), 100);
            this.Listener.Start(this.OnNanoListenerTransaction, this.OnNanoListenerError);
            foreach (string knownHash in this.Storage.KnownHashes)
            {
                this.Listener.AddKnownHash(knownHash);
            }

            // Twitch
            ConnectionCredentials credentials   = new ConnectionCredentials(this.Storage.TwitchUsername, twitchAuthToken);
            ClientOptions         clientOptions = new ClientOptions {
                MessagesAllowedInPeriod = 100, ThrottlingPeriod = TimeSpan.FromSeconds(30)
            };
            WebSocketClient customClient = new WebSocketClient(clientOptions);

            this.Twitch = new TwitchClient(customClient);
            this.Twitch.Initialize(credentials, this.Storage.TwitchChannel);
            this.Twitch.Connect();

            // Twitch Log
            this.Twitch.OnLog += (s, e) =>
            {
                lock (this.messages)
                {
                    string message = $"{e.DateTime}: {e.Data}";
                    this.messages.Add(new NanoTwitchBotMessage(NanoTwitchBotMessageType.Information, message));
                }
            };

            // Twitch Connected
            this.Twitch.OnConnected += (s, e) =>
            {
                if (this.Twitch.IsConnected)
                {
                    string chatMessageOnline = this.Storage.ChatMessageOnline;
                    this.Twitch.SendMessage(new JoinedChannel(this.Storage.TwitchChannel), chatMessageOnline);
                }
            };

            // Twitch Message Received
            this.Twitch.OnMessageReceived += (s, e) =>
            {
                // Message
                string message = e.ChatMessage.Message;

                // !nano Command
                if (message.StartsWith("!nano"))
                {
                    if (this.Twitch.IsConnected)
                    {
                        string chatMessageInfo = string.Format(this.Storage.ChatMessageInfo, this.Storage.NanoAccount);
                        this.Twitch.SendMessage(new JoinedChannel(this.Storage.TwitchChannel), chatMessageInfo);
                    }
                }

                // !nano_add Command
                if (message.StartsWith("!nano_add"))
                {
                    string account = message.Substring("!nano_add".Length);
                    account = account.Trim();
                    this.AddAddress(e.ChatMessage.DisplayName, account, true);
                    if (this.Twitch.IsConnected)
                    {
                        string chatMessageAdded = string.Format(this.Storage.ChatMessageAdded, e.ChatMessage.DisplayName);
                        this.Twitch.SendMessage(new JoinedChannel(this.Storage.TwitchChannel), chatMessageAdded);
                    }
                }

                // !nano_delete Command
                if (message.StartsWith("!nano_delete"))
                {
                    this.DeleteAddress(e.ChatMessage.DisplayName, true);
                    if (this.Twitch.IsConnected)
                    {
                        string chatMessageDeleted = string.Format(this.Storage.ChatMessageDeleted, e.ChatMessage.DisplayName);
                        this.Twitch.SendMessage(new JoinedChannel(this.Storage.TwitchChannel), chatMessageDeleted);
                    }
                }
            };
        }
Exemple #11
0
 public SendingTests()
 {
     _client = NanoClient.GetClient(Configuration.BaseUrl);
 }
Exemple #12
0
 public PeersTests()
 {
     _client = NanoClient.GetClient(Configuration.BaseUrl);
 }
 public AudioChannelBase(NanoClient client, NanoChannelId id)
     : base(client, id)
 {
 }
 public VideoChannelBase(NanoClient client, NanoChannelId id)
     : base(client, id)
 {
 }
        public override async Task <CommandResult> ExecuteAsync(CancellationToken cancel)
        {
            if (TokenFilePath != null)
            {
                using (FileStream fs = File.Open(TokenFilePath, FileMode.Open))
                {
                    AuthService = await AuthenticationService.LoadFromJsonFileStream(fs);

                    await AuthService.AuthenticateAsync();
                }

                await AuthService.DumpToJsonFileAsync(TokenFilePath);
            }

            Console.WriteLine($"Connecting to {Hostname}...");

            GamestreamSession session = null;
            SmartGlassClient  Client  = null;

            try
            {
                Client = await SmartGlassClient.ConnectAsync(Hostname,
                                                             AuthService == null?null : AuthService.XToken.UserInformation.Userhash,
                                                             AuthService == null?null : AuthService.XToken.Jwt);
            }
            catch (SmartGlassException e)
            {
                Console.WriteLine($"Failed to connect: {e.Message}");
                return(CommandResult.RuntimeFailure);
            }
            catch (TimeoutException)
            {
                Console.WriteLine($"Timeout while connecting");
                return(CommandResult.RuntimeFailure);
            }

            var broadcastChannel = Client.BroadcastChannel;

            var config = GamestreamConfiguration.GetStandardConfig();

            try
            {
                session = await broadcastChannel.StartGamestreamAsync(config);
            }
            catch (Exception e)
            {
                Console.WriteLine($"Failed to send StartGamestream: {e.Message}");
                return(CommandResult.RuntimeFailure);
            }

            Console.WriteLine($"Connecting to Nano, TCP: {session.TcpPort}, UDP: {session.UdpPort}");
            var nano = new NanoClient(Hostname, session);

            try
            {
                Console.WriteLine($"Running protocol init...");
                await nano.InitializeProtocolAsync();

                await nano.OpenInputChannelAsync(1280, 720);

                await nano.OpenChatAudioChannelAsync(
                    new Nano.Packets.AudioFormat(1, 24000, AudioCodec.Opus));

                Console.WriteLine("Adding FileConsumer");
                FileConsumer consumer = new FileConsumer("nanostream");
                nano.AddConsumer(consumer);

                Console.WriteLine("Initializing AV stream (handshaking)...");
                await nano.InitializeStreamAsync(nano.AudioFormats[0],
                                                 nano.VideoFormats[0]);

                Console.WriteLine("Starting stream...");
                await nano.StartStreamAsync();
            }
            catch (Exception e)
            {
                Console.WriteLine($"Failed to initialize gamestream: {e}");
                return(CommandResult.RuntimeFailure);
            }

            Console.WriteLine("Stream is running");

            var loop = new Loop(typeof(SessionCommandType));

            loop.Execute();

            return(CommandResult.Success);
        }
Exemple #16
0
 public WalletTests()
 {
     _client = NanoClient.GetClient(Configuration.BaseUrl);
 }
Exemple #17
0
 public AccountTests()
 {
     _client = NanoClient.GetClient(Configuration.BaseUrl);
 }
 public VideoChannel(NanoClient client)
     : base(client, NanoChannelId.Video)
 {
     HandshakeDone = false;
 }
 public ChatAudioChannel(NanoClient client)
     : base(client, NanoChannelId.ChatAudio)
 {
     HandshakeDone = false;
 }
Exemple #20
0
 public NetworkTests()
 {
     _client = NanoClient.GetClient(Configuration.BaseUrl);
 }
        static void Main(string[] args)
        {
            var printHelp     = false;
            var fullscreen    = false;
            var useController = false;
            var ipAddress     = String.Empty;
            var tokenPath     = String.Empty;

            var p = new OptionSet {
                { "h|?|help", "Show this help and exit", v => printHelp = v != null },
                { "fullscreen", "Start in fullscreen mode", v => fullscreen = v != null },
                { "controller", "Use controller on this device", v => useController = v != null },
                { "a|address=", "Specify {IP ADDRESS} of target console", v =>
                  {
                      if (!VerifyIpAddress(v))
                      {
                          throw new OptionException("Invalid IP Address", "address");
                      }
                      ipAddress = v;
                  } },
                { "t|token=", "Specify {TOKEN FILEPATH} for connecting authenticated", v =>
                  {
                      if (!File.Exists(v))
                      {
                          throw new OptionException("Invalid tokenpath", "token");
                      }
                      tokenPath = v;
                  } }
            };

            List <string> extraArgs;

            try
            {
                extraArgs = p.Parse(args);
            }
            catch (OptionException e)
            {
                Console.WriteLine($"Failed parsing parameter \'{e.OptionName}\': {e.Message}");
                Console.WriteLine("Try 'SmartGlass.Nano.FFmpeg --help' for more information");
                return;
            }

            if (printHelp || String.IsNullOrEmpty(ipAddress))
            {
                Console.WriteLine("Usage: SmartGlass.Nano.FFmpeg [parameters]");
                Console.WriteLine("Gamestream from xbox one");
                Console.WriteLine();
                Console.WriteLine("Parameters:");
                p.WriteOptionDescriptions(Console.Out);
                return;
            }

            if (!Authenticate(tokenPath))
            {
                Console.WriteLine("Connecting anonymously to console, no XBL token available");
            }

            string hostName = ipAddress;

            Console.WriteLine($"Connecting to console {hostName}...");
            GamestreamConfiguration config = GamestreamConfiguration.GetStandardConfig();

            GamestreamSession session = ConnectToConsole(ipAddress, config).GetAwaiter().GetResult();

            if (session == null)
            {
                Console.WriteLine("Failed to connect to console!");
                return;
            }

            Console.WriteLine(
                $"Connecting to NANO // TCP: {session.TcpPort}, UDP: {session.UdpPort}");

            NanoClient nano = InitNano(hostName, session).GetAwaiter().GetResult();

            if (nano == null)
            {
                Console.WriteLine("Nano failed!");
                return;
            }

            // SDL / FFMPEG setup
            SdlProducer producer = new SdlProducer(nano, _audioFormat, _videoFormat, fullscreen, useController);

            nano.AudioFrameAvailable += producer.Decoder.ConsumeAudioData;
            nano.VideoFrameAvailable += producer.Decoder.ConsumeVideoData;

            producer.MainLoop();

            // finally (dirty)
            Process.GetCurrentProcess().Kill();
        }
Exemple #22
0
 public InputChannelBase(NanoClient client, NanoChannelId id)
     : base(client, id)
 {
 }
Exemple #23
0
 public BootstrapTests()
 {
     _client = NanoClient.GetClient(Configuration.BaseUrl);
 }
Exemple #24
0
        static void Main(string[] args)
        {
            Native.AllocConsole();

            if (args.Length > 0)
            {
                Config.CurrentMapping.Init(args);
                _tokenFilePath = Config.CurrentMapping.TokenFilePath;
                Authenticate();
            }
            else
            {
                _tokenFilePath = Shell.WriteReadLine("tokenFilePath: ");
                Config.CurrentMapping.TokenFilePath = _tokenFilePath;

                if (File.Exists(_tokenFilePath))
                {
                    Authenticate();
                }
                else
                {
                    Shell.Warning("\'{0}\' file not found.\n", _tokenFilePath);
                    Shell.WriteLine("1) Open following URL in your WebBrowser:\n\n{0}\n\n"
                                    + "2) Authenticate with your Microsoft Account\n"
                                    + "3) Paste returned URL from addressbar: \n"
                                    , AuthenticationService.GetWindowsLiveAuthenticationUrl());

                    Authenticate(Shell.WriteReadLine());
                }

                if (Config.CurrentMapping.IP.Length == 0)
                {
                    Shell.WriteLine("{0,-15} {1,-36} {2,-15} {3,-16}", "Name", "HardwareId", "Address", "LiveId");
                    IEnumerable <Device> devices = Device.DiscoverAsync().GetAwaiter().GetResult();
                    foreach (Device device in devices)
                    {
                        Shell.WriteLine("{0,-15} {1,-36} {2,-15} {3,-16}"
                                        , device.Name
                                        , device.HardwareId
                                        , device.Address
                                        , device.LiveId);
                    }

                    string ip = Shell.WriteReadLine("Input IP Address or hostname: ");
                    Config.CurrentMapping.Init($"{_tokenFilePath} ${ip}");
                }
            }

            // Get general gamestream configuration
            GamestreamConfiguration config = GamestreamConfiguration.GetStandardConfig();

            // Modify standard config, if desired

            /*
             * GAME_STREAMING_VERY_HIGH_QUALITY_SETTINGS: 12000000,1080,60,59,0,10,40,170
             * GAME_STREAMING_HIGH_QUALITY_SETTINGS: 8000000,720,60,59,0,10,40,170
             * GAME_STREAMING_MEDIUM_QUALITY_SETTINGS: 6000002,720,60,3600,0,40,70,200
             * GAME_STREAMING_LOW_QUALITY_SETTINGS: 3000001,480,30,3600,0,40,70,200
             *
             * 12000000 = 12Mbit/s = 12Mbps
             *
             * SETTINGS:
             * Unknown1,Unknown2,Unknown3,Unknown4,Unknown5,Unknown6,Unknown7,Unknown8
             * Unknown1 UrcpMaximumRate         FIXME: Or AudioBufferLengthHns, both??
             * Unknown2 VideoMaximumHeight
             * Unknown3 VideoMaximumFrameRate
             * Unknown4                         FIXME: Which is Unknown4?
             * Unknown5                         FIXME: Which is Unknown5?
             * Unknown6 AudioSyncMinLatency
             * Unknown7 AudioSyncDesiredLatency
             * Unknown8 AudioSyncMaxLatency
             *
             * refer to: https://github.com/OpenXbox/xbox-smartglass-nano-python/issues/7
             * standard: GAME_STREAMING_MEDIUM_QUALITY_SETTINGS
             */
            //config.UrcpMaximumRate = 12000000;// 2后面6个0
            //config.VideoMaximumHeight = 480;
            //config.VideoMaximumFrameRate = 30;
            //config.Unknown4 = 3600;
            //config.Unknown5 = 0;
            //config.AudioSyncMinLatency = 40;
            //config.AudioSyncDesiredLatency = 70;
            //config.AudioSyncMaxLatency = 200;
            config.UrcpMaximumRate       = Config.CurrentMapping.Quality.Unknown1;
            config.VideoMaximumHeight    = Config.CurrentMapping.Quality.Unknown2;
            config.VideoMaximumFrameRate = Config.CurrentMapping.Quality.Unknown3;
            //config.Unknown4 = Config.CurrentMapping.Quality.Unknown4;
            //config.Unknown5 = Config.CurrentMapping.Quality.Unknown5;
            config.AudioSyncMinLatency     = Config.CurrentMapping.Quality.Unknown6;
            config.AudioSyncDesiredLatency = Config.CurrentMapping.Quality.Unknown7;
            config.AudioSyncMaxLatency     = Config.CurrentMapping.Quality.Unknown8;

            config.VideoMaximumWidth = TVResolution.Width(config.VideoMaximumHeight);

            Shell.WriteLine("Connecting to {0}...", Config.CurrentMapping.IP);
            GamestreamSession session = ConnectToConsole(Config.CurrentMapping.IP, config);

            Shell.WriteLine("Connecting to NANO // TCP: {0}, UDP: {1}", session.TcpPort, session.UdpPort);
            Nano = new NanoClient(Config.CurrentMapping.IP, session);
            try
            {
                // General Handshaking & Opening channels
                Shell.WriteLine("Running protocol init...");
                Nano.InitializeProtocolAsync().Wait();

                // Start Controller input channel
                Nano.OpenInputChannelAsync(Nano.Video.Width, Nano.Video.Height).Wait();

                // Audio & Video client handshaking
                // Sets desired AV formats
                AudioFormat = Nano.AudioFormats[0];
                VideoFormat = Nano.VideoFormats[0];

                Shell.WriteLine("Initializing AV stream (handshaking)...");
                Nano.InitializeStreamAsync(AudioFormat, VideoFormat).Wait();

                // Start ChatAudio channel
                // TODO: Send opus audio chat samples to console
                ChatAudioFormat = new AudioFormat(1, 24000, AudioCodec.Opus);
                Nano.OpenChatAudioChannelAsync(ChatAudioFormat).Wait();

                // Tell console to start sending AV frames
                Shell.WriteLine("Starting stream...");
                Nano.StartStreamAsync().Wait();
                Shell.Note("Stream is running");
            }
            catch (Exception e)
            {
                throw Shell.Log("Failed to init Nano, error: {e.Message}", e, e.Message);
            }

#if !DEBUG
            Trace.Listeners.Clear();
            Trace.Listeners.Add(new Logger(_tokenFilePath));
            Native.FreeConsole();
#endif

            // Run a mainloop, to gather controller input events or similar
            Application.SetHighDpiMode(HighDpiMode.SystemAware);
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new Xstream());

            // finally (dirty)
            Process.GetCurrentProcess().Kill();
        }
Exemple #25
0
 public UncheckedBlocksTests()
 {
     _client = NanoClient.GetClient(Configuration.BaseUrl);
 }
 public InputFeedbackChannel(NanoClient client)
     : base(client, NanoChannelId.InputFeedback)
 {
     HandshakeDone = false;
 }
Exemple #27
0
        static void Main()
        {
            AllocConsole();

            Console.Write("tokenFilePath: ");
            string tokenFilePath = Console.ReadLine();

            AuthenticationService auth;

            if (!File.Exists(tokenFilePath))
            {
                Shell.WriteLine("Warning: '{0}' file not found.\n", tokenFilePath);

                string reqURL = AuthenticationService.GetWindowsLiveAuthenticationUrl();

                Console.WriteLine("1) Open following URL in your WebBrowser:\n\n{0}\n\n" +
                                  "2) Authenticate with your Microsoft Account\n" +
                                  "3) Paste returned URL from addressbar: \n", reqURL);

                // Call requestUrl via WebWidget or manually and authenticate

                try
                {
                    string url = Console.ReadLine();
                    WindowsLiveResponse rep = AuthenticationService.ParseWindowsLiveResponse(url);
                    auth = new AuthenticationService(rep);

                    auth.Authenticate();
                }
                catch (Exception e)
                {
                    Shell.WriteLine($"Error: Authentication failed, error: {e.Message}");
                    Shell.PressAnyKeyToContinue();
                    return;
                }

                Console.WriteLine(auth.XToken);
                Console.WriteLine(auth.UserInformation);

                // Save token to JSON

                FileStream tokenOutputFile = null;
                try
                {
                    tokenOutputFile = new FileStream(tokenFilePath, FileMode.Create);
                }
                catch (Exception e)
                {
                    Shell.WriteLine("Error: Failed to open token outputfile \'{0}\', error: {1}",
                                    tokenOutputFile, e.Message);
                    Shell.PressAnyKeyToContinue();
                    return;
                }
                auth.DumpToFile(tokenOutputFile);
                tokenOutputFile.Close();

                Console.WriteLine("Storing tokens to file \'{0}\' on successful auth",
                                  tokenOutputFile.Name);
            }
            else
            {
                // Load token from JSON

                FileStream fs = new FileStream(tokenFilePath, FileMode.Open);
                auth = AuthenticationService.LoadFromFile(fs);
                try
                {
                    auth.Authenticate();
                }
                catch (Exception e)
                {
                    Shell.WriteLine($"Error: Failed to refresh XBL tokens, error: {e.Message}");
                    Shell.PressAnyKeyToContinue();
                    return;
                }
                fs.Close();
            }

            UserHash = auth.XToken.UserInformation.Userhash;
            XToken   = auth.XToken.Jwt;

            Discover().Wait();

            Console.Write("Input IP Address or hostname: ");
            string addressOrHostname = Console.ReadLine();

            Console.WriteLine($"Connecting to {addressOrHostname}...");
            SmartGlassClient client;

            try
            {
                Task <SmartGlassClient> connect = SmartGlassClient.ConnectAsync(
                    addressOrHostname, UserHash, XToken);

                // 如果Task失败了GetAwaiter()会直接抛出异常,而Task.Wait()会抛出AggregateException
                client = connect.GetAwaiter().GetResult();
            }
            catch (Exception e)
            {
                if (e is SmartGlassException)
                {
                    Shell.WriteLine($"Error: Failed to connect: {e.Message}");
                }
                else if (e is TimeoutException)
                {
                    Shell.WriteLine($"Error: Timeout while connecting: {e.Message}");
                }
                else
                {
                    Shell.WriteLine($"Error: {e}");
                }

                Shell.PressAnyKeyToContinue();
                return;
            }

            // Get general gamestream configuration
            GamestreamConfiguration config = GamestreamConfiguration.GetStandardConfig();
            // Modify standard config, if desired

            GamestreamSession session = client.BroadcastChannel.StartGamestreamAsync(config)
                                        .GetAwaiter().GetResult();

            Console.WriteLine($"Connecting to NANO // TCP: {session.TcpPort}, UDP: {session.UdpPort}");

            Console.WriteLine($"Running protocol init...");
            Nano = new NanoClient(addressOrHostname, session);
            try
            {
                // General Handshaking & Opening channels
                Nano.InitializeProtocolAsync().Wait();

                // Start Controller input channel
                Nano.OpenInputChannelAsync(1280, 720).Wait();

                //IConsumer consumer = /* initialize consumer */;
                //nano.AddConsumer(consumer);

                // Start consumer, if necessary
                //consumer.Start();

                // Audio & Video client handshaking
                // Sets desired AV formats
                Console.WriteLine("Initializing AV stream (handshaking)...");

                AudioFormat = Nano.AudioFormats[0];
                VideoFormat = Nano.VideoFormats[0];

                Nano.InitializeStreamAsync(AudioFormat, VideoFormat).Wait();

                // Start ChatAudio channel
                // TODO: Send opus audio chat samples to console
                ChatAudioFormat = new AudioFormat(1, 24000, AudioCodec.Opus);
                Nano.OpenChatAudioChannelAsync(ChatAudioFormat).Wait();

                // Tell console to start sending AV frames
                Console.WriteLine("Starting stream...");

                Nano.StartStreamAsync().Wait();

                Shell.WriteLine("Note: Stream is running");
            }
            catch (Exception e)
            {
                Shell.WriteLine($"Error: Failed to init Nano, error: {e}");
                Shell.PressAnyKeyToContinue();
                return;
            }

            // Run a mainloop, to gather controller input events or similar

            FreeConsole();

            Application.SetHighDpiMode(HighDpiMode.SystemAware);
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new Xstream());

            // finally (dirty)
            Process.GetCurrentProcess().Kill();
        }