2012-08-08 69 views
5

我正在C#中創建一個庫,以使用Airplay協議將照片和視頻發送到我的Apple TV(特別與第3代一起工作,但希望這對此應該不重要)。TcpClient或HttpWebRequest到30秒後結束的Apple TV?

https://airlib.codeplex.com/

所有的Airplay的命令是HTTP端口70按照該規範: http://nto.github.com/AirPlay.html

我已經成功地獲取照片和視頻到Apple TV上播放,但沒有不管我做什麼,AppleTV只會播放價值30秒的視頻。看起來好像我發佈播放命令的C#客戶端在30秒內斷開連接,導致AppleTV結束播放會話。

原因,我想這樣的:

  • 終止客戶端應用程序完全產生相同的行爲,等待30秒(基本上是強制連接關閉)。
  • 手動關閉HttpWebRequest或TcpClient連接會產生相同的行爲(通過播放會話中途)。
  • 無論持有斷點多長時間以防止GetResponse()調用,視頻始終在WebRequest開始發送消息後的30秒內超時。
  • 對視頻使用不同的來源(IIS,外部網絡服務器)不會改變行爲。
  • 即使視頻在AppleTV上緩存並且不重新流,超時仍會發生。

我很確定客戶端請求需要在整個視頻的「播放」過程中保持連接狀態,並且據我所知,我已經編寫了它來完成此操作。我真的很聰明。我已經嘗試了所有我能想到的方法,包括以HttpWebRequest和原始TcpClient的方式執行請求(兩者都工作但都超時),將接收/發送超時設置爲瘋狂的數字,並循環讀取Tcp流確保有「活動」。

就好像AppleTV期待我發送「嘿,繼續播放」的信息,但我還沒有看到任何來自網絡上的任何來源的信息。我希望基於我缺乏Http/Tcp知識,這只是我沒有做的愚蠢的事情。

這裏是我的代碼:

Uri url = "http://somevideo.com/video.mov"; 
    float startPosition = 0;   
    TcpClient tcpClient = new TcpClient("192.168.1.20",7000); 
    tcpClient.ReceiveTimeout = 100000; 
    tcpClient.SendTimeout = 100000; 

    //get the client stream to read data from. 
    NetworkStream clientStream = tcpClient.GetStream(); 

    string body = 
    "Content-Location: " + url + "\n" + 
    "Start-Position: " + startPosition + "\n"; 

    string request = "POST /play HTTP/1.1\n" + 
    "User-Agent: MediaControl/1.0\n" + 
    "Content-Type: text/parameters\n" + 
    "Content-Length: " + Encoding.ASCII.GetBytes(body).Length + "\n" +   
    "X-Apple-Session-ID:" + _sessionGuid.ToString() + "\n\n"; 

    sendMessage(clientStream, request); 
    sendMessage(clientStream, body); 

    byte[] myReadBuffer = new byte[1024]; 
    StringBuilder myCompleteMessage = new StringBuilder(); 
    int numberOfBytesRead = 0; 

    //incoming message might be bigger than the buffer 
    do 
    { 
     try 
     { 
      numberOfBytesRead = clientStream.Read(myReadBuffer, 0, myReadBuffer.Length); 
      myCompleteMessage.Append(Encoding.ASCII.GetString(myReadBuffer, 0, numberOfBytesRead)); 
      Thread.Sleep(10);//let the iOS device catch up sending data 
     } 
     catch (System.IO.IOException) { } 
    } while (tcpClient.Connected); //check if it's connected before checking for data available, as maybe the program might get quit and the sockets closed halfway through a read 

注意:使用telnet我能夠連接到AppleTV的端口7000和在此命令,其播放整個視頻貼:

POST /play HTTP/1.1 
User-Agent: MediaControl/1.0 
Content-Type: text/parameters 
Content-Length: 89 
X-Apple-Session-ID:fb6d816a-a5ad-4e8f-8830-9642b6e6eb35 

Content-Location: http://192.168.1.11:82/2012/2012_03_11/IMG_1328.MOV 
Start-Position: 0 

我在端口82上運行卡西尼網絡服務器,但這也適用於IIS。這提供了進一步的證據,即.Net堆棧在30秒內在引擎蓋下做了一些事情,導致斷開連接。

回答

5

我終於明白了。這不是.Net代碼會導致連接中斷,而是Apple TV本身。通過wireshark,我可以在30秒後看到來自AppleTV的正確的Ack和Fin消息,但沒有收到該連接上的任何新消息。爲了解決這個問題,我想通過玩弄Telnet,AppleTV似乎不在乎你發送的內容,只要你定期發送它,這似乎能夠保持連接的連貫性。

使用HttpWebRequest發送/接收部分是非常容易的。它設計用於標準的Http請求和響應,如果您需要做其他任何事情,只需啓動新的HttpWebRequest而不是使用現有的HttpWebRequest。嘗試在相同的HttpWebRequest錯誤中發送第二條消息。所以我不得不使用TcpClient,並且不得不返回結束。

/// <summary> 
    /// Starts a video. 
    /// </summary> 
    /// <param name="url">The URL of the video to play.</param> 
    /// <param name="startPosition">The start position of the video. This value must be between 0 and 1</param> 
    public void StartVideo(Uri url, float startPosition = 0) 
    { 
     if (startPosition > 1) 
     { 
      throw new ArgumentException("Start Position must be between 0 and 1"); 
     } 

     TcpClient tcpClient = new TcpClient("192.168.1.20", 7000); 
     tcpClient.ReceiveTimeout = 100000; 
     tcpClient.SendTimeout = 100000; 

     //get the client stream to read data from. 
     NetworkStream clientStream = tcpClient.GetStream(); 

     string body = 
     "Content-Location: " + url + "\n" + 
     "Start-Position: " + startPosition + "\n"; 

     string request = "POST /play HTTP/1.1\n" + 
     "User-Agent: MediaControl/1.0\n" + 
     "Content-Type: text/parameters\n" + 
     "Content-Length: " + Encoding.ASCII.GetBytes(body).Length + "\n" + 
     "X-Apple-Session-ID:" + _sessionGuid.ToString() + "\n\n"; 

     //Send the headers 
     sendMessage(clientStream, request); 
     //Send the body 
     sendMessage(clientStream, body); 

     //Get the response 
     byte[] myReadBuffer = new byte[1024]; 
     StringBuilder myCompleteMessage = new StringBuilder(); 
     int numberOfBytesRead = 0; 
     numberOfBytesRead = clientStream.Read(myReadBuffer, 0, myReadBuffer.Length); 
     myCompleteMessage.Append(Encoding.ASCII.GetString(myReadBuffer, 0, numberOfBytesRead)); 

     //Now start doing a "keepalive" 
     while (true) 
     { 
      //Simply send the characters "ok" every two seconds 
      sendMessage(clientStream, "ok"); 
      Thread.Sleep(2000); 
     }      
    } 

    /// <summary> 
    /// Sends a message across the NetworkStream 
    /// </summary> 
    /// <param name="clientStream">The stream to send the message down</param> 
    /// <param name="message">The message to send</param> 
    public void sendMessage(NetworkStream clientStream, string message) 
    { 
     byte[] buffer = new ASCIIEncoding().GetBytes(message); 
     try 
     { 
      clientStream.Write(buffer, 0, buffer.Length); 
      clientStream.Flush(); 
     } 
     catch (System.IO.IOException e) 
     { 
      Debug.WriteLine("IOException: " + e.Message); 
     } 
    } 

顯然這不是最終答案,但這是最低限度的工作。如果有人找出真正的Apple硬件發送的是「OK」,請添加一個註釋。

+0

使用Keep-Alive標題可能會解決這個問題嗎? – 2013-08-22 20:27:42

+0

這實際上是我嘗試的第一件事,不幸的是沒有幫助。好主意雖然! – ExcaliburVT 2013-08-23 00:20:34

+0

對你而言可能已經太晚了,但對其他人很有幫助:爲你的會話實現「反向HTTP」,或者只是輪詢「/ scrub」來保持播放的活性 - 也可以使用scrub獲得電影的持續時間和位置。 – coyer 2016-06-10 16:38:17