5

我正在用 C# 创建一个库,以使用 Airplay 协议将照片和视频发送到我的 Apple TV(特别是与第 3 代一起使用,但希望这不重要)。

https://airlib.codeplex.com/

根据此规范,Airplay 的所有命令都是端口 70 上的 HTTP: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 我可以连接到端口 7000 上的 AppleTV 并粘贴此命令以播放整个视频:

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 上运行 Cassini Webserver,但这也适用于 IIS。这提供了进一步的证据,表明 .Net 堆栈在 30 秒内正在做一些导致断开连接的事情。

4

1 回答 1

5

我终于想通了。杀死连接的不是 .Net 代码,而是 Apple TV 本身。使用wireshark,我能够看到来自AppleTV的正确Ack和Fin消息,这些消息在30秒后没有在该连接上收到任何新消息。为了解决这个问题,我通过玩 Telnet 发现 AppleTV 似乎并不关心你发送什么,只要你定期发送它,这似乎可以保持连接活跃。

使用 HttpWebRequest 发送/接收部分是相当固定的。它是为标准的 Http 请求和响应而设计的,如果您需要做任何其他事情,您只需启动一个新的 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”,请添加注释。

于 2012-08-09T04:33:27.970 回答