5

私はC#でライブラリを作成して、Airplayプロトコルを使用して写真とビデオをApple TVに送信するように取り組んでいます(特に第3世代で動作しますが、これが問題にならないことを願っています)。

https://airlib.codeplex.com/

Airplayのすべてのコマンドは、次の仕様に従ってポート70でHTTPです:http: //nto.github.com/AirPlay.html

私は写真とビデオの両方をAppleTVで再生することに成功しましたが、何をしてもAppleTVは30秒分のビデオしか再生しません。playコマンドを発行するC#クライアントが30秒で切断されているように見えます。これにより、AppleTVは再生セッションを終了します。

私がこれを考える理由:

  • クライアントアプリを終了すると、30秒待つのと同じ動作が完全に発生します(基本的に接続を強制的に閉じます)。
  • HttpWebRequestまたはTcpClient接続を手動で閉じると、同じ動作が発生します(再生セッションの途中)。
  • GetResponse()呼び出しを防ぐためにブレークポイントを保持している時間に関係なく、WebRequestがメッセージの送信を開始してから30秒後にビデオが常にタイムアウトします。
  • ビデオに別のソース(IIS、外部Webサーバー)を使用しても、動作は変わりません。
  • ビデオがAppleTVにキャッシュされ、再ストリーミングされなかった後でも、タイムアウトが発生します。

私は、クライアントのリクエストがビデオの「再生」の間ずっと接続されている必要があると確信しています。私の知る限り、それを行うためにコーディングしました。私は本当に私の知恵の終わりにいます。HttpWebRequestと生のTcpClient(両方とも機能しますが両方ともタイムアウト)としてリクエストを実行すること、Recieve / Sendタイムアウトをクレイジーな数値に設定すること、Tcpストリームの読み取りをループすることなど、考えられるすべてのことを試しました。 「活動」があることを確認します。

AppleTVは私が「ねえ、再生を続けて」というメッセージを送信することを期待しているようですが、Web上のどのソースからもそのようなものはまだ見ていません。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でCassiniWebサーバーを実行していますが、これはIISでも機能します。これは、.Netスタックが30秒で内部で何かを実行して切断を引き起こしているというさらなる証拠を提供します。

4

1 に答える 1

5

私はついにそれを理解しました。.Netコードが接続を切断したのではなく、AppleTV自体でした。wiresharkを使用すると、その接続で新しいメッセージを受信しなかった30秒後に、AppleTVからの適切なAckおよびFinメッセージを確認できました。この問題を解決するために、Telnetをいじってみたところ、AppleTVは、定期的に何かを送信している限り、AppleTVは送信内容を気にしないようであり、接続を維持しているようです。

HttpWebRequestを使用すると、送信/受信部分はかなり缶詰になります。これは、標準のHttpリクエストとレスポンス用に設計されており、他に何かを行う必要がある場合は、既存のHttpWebRequestを使用するのではなく、新しいHttpWebRequestを開始するだけです。同じHttpWebRequestエラーで2番目のメッセージを送信しようとするとエラーが発生します。

そのため、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);
        }
    }

明らかにこれが最終的な答えではありませんが、これはそれを機能させるための最低限のことでした。「ok」の代わりに実際のAppleハードウェアが何を送信しているかを誰かが理解した場合は、メモを追加してください。

于 2012-08-09T04:33:27.970 に答える