postlund / pyatv

A client library for Apple TV and AirPlay devices
https://pyatv.dev
MIT License
891 stars 100 forks source link

Play RTSP audio stream via AirPlay #387

Open ashald opened 4 years ago

ashald commented 4 years ago

Describe the bug I have an audio stream on my Raspberry Pi (from a record player) that I want to stream to my AppleTV with pyatvb and it doesn't work. Not sure if there is an issue on pyatv or I'm doing something wrong/trying to do something that is not supported.

To Reproduce The easiest way to reproduce the streaming setup is:

cvlc -vvv some-music.mp3 --sout '#transcode{acodec=mp4a,ab=320,channels=2}:rtp{sdp=rtsp://0.0.0.0:8888/}'

In reality I the input is an Alsa audio device but an mp3 file gives the same effect.

Once that's running I'm trying to run the following:

atvremote -i ${ID} --mrp-credentials ${MRP} --airplay-credentials ${ATV} play_url='rtsp://alpha.local:8888/'

The AppleTV screen goes dark for few moments as though it's trying to start playback (with a visible spinner) and then it "crashes" back to the main menu.

Expected behavior Apple TV plays audio from the stream. When I open the RTSP stream from another host it play normally.

System Setup (please complete the following information):

Additional context Debug output:

DEBUG: Discovering devices for 3 seconds
DEBUG: Auto-discovered My Music on alpha at 10.0.10.237:3689 (Protocol.DMAP)
DEBUG: Auto-discovered TV at 10.0.11.1:49152 (Protocol.MRP)
DEBUG: Auto-discovered TV at 10.0.11.1:7000 (Protocol.AirPlay)
DEBUG: Auto-discovered Left at 10.0.10.56:7000 (Protocol.AirPlay)
DEBUG: Auto-discovered Right at 10.0.10.231:7000 (Protocol.AirPlay)
INFO: Auto-discovered None at None
DEBUG: Connected to device
DEBUG: >> Send (Data=080f122463653332653436342d613938652d343935372d623332622d3634393665363761653065312000a20183010a2438653762306438632d336461662d346363622d393139302d666633626631323162353038120570796174761a066950686f6e6522063137423131312a12636f6d2e6170706c652e545652656d6f746532063334342e32383801404d480150016211636f6d2e6170706c652e54564d75736963680170017801880102a80101b00101)
DEBUG: >> Send: Protobuf=type: DEVICE_INFO_MESSAGE
identifier: "ce32e464-a98e-4957-b32b-6496e67ae0e1"
priority: 0
[deviceInfoMessage] {
  uniqueIdentifier: "8e7b0d8c-3daf-4ccb-9190-ff3bf121b508"
  name: "pyatv"
  localizedModelName: "iPhone"
  systemBuildVersion: "17B111"
  applicationBundleIdentifier: "com.apple.TVRemote"
  applicationBundleVersion: "344.28"
  protocolVersion: 1
  lastSupportedMessageType: 77
  supportsSystemPairing: true
  allowsPairing: true
  systemMediaApplication: "com.apple.TVMusic"
  supportsACL: true
  supportsSharedQueue: true
  supportsExtendedMotion: true
  sharedQueueVersion: 2
  deviceClass: 1
  logicalDeviceCount: 1
}

DEBUG: << Receive (Data=e002080f122463653332653436342d613938652d343935372d623332622d3634393665363761653065312000a201b2020a2442463738314534412d383445412d343535372d383036382d433331364631424233394641120254561a094170706c65c2a05456220631374b3434392a16636f6d2e6170706c652e6d6564696172656d6f7465643801404d480150016211636f6d2e6170706c652e54564d757369636801700178018801029a012439363542343939312d343033412d343532452d383839362d304132444545423936333941a2011134303a63623a63303a64303a64343a3530a80104b00101c00100d2012438434143443031392d364342322d344343412d383834462d433631433734424133433443e80101f00101fa0112636f6d2e6170706c652e706f64636173747382022438434143443031392d364342322d344343412d383834462d4336314337344241334334438a02095456416972506c6179)
DEBUG: << Receive: Protobuf=type: DEVICE_INFO_MESSAGE
identifier: "ce32e464-a98e-4957-b32b-6496e67ae0e1"
priority: 0
[deviceInfoMessage] {
  uniqueIdentifier: "BF781E4A-84EA-4557-8068-C316F1BB39FA"
  name: "TV"
  localizedModelName: "Apple\302\240TV"
  systemBuildVersion: "17K449"
  applicationBundleIdentifier: "com.apple.mediaremoted"
  protocolVersion: 1
  lastSupportedMessageType: 77
  supportsSystemPairing: true
  allowsPairing: true
  systemMediaApplication: "com.apple.TVMusic"
  supportsACL: true
  supportsSharedQueue: true
  supportsExtendedMotion: true
  sharedQueueVersion: 2
  deviceUID: "965B4991-403A-452E-8896-0A2DEEB9639A"
  managedConfigDeviceID: "40:cb:c0:d0:d4:50"
  deviceClass: 4
  logicalDeviceCount: 1
  isProxyGroupPlayer: false
  groupUID: "8CACD019-6CB2-4CCA-884F-C61C74BA3C4C"
  isGroupLeader: true
  isAirplayActive: true
  systemPodcastApplication: "com.apple.podcasts"
  enderDefaultGroupUID: "8CACD019-6CB2-4CCA-884F-C61C74BA3C4C"
  airplayReceivers: "TVAirPlay"
}

DEBUG: >> Send (Data=08222000ba022f0a250601010320b4a698a675602d04cc19f9eceb9c6a353ea17004c88273d9c98bd79a037052111000180020002800)
DEBUG: >> Send: Protobuf=type: CRYPTO_PAIRING_MESSAGE
priority: 0
[cryptoPairingMessage] {
  pairingData: "\006\001\001\003 \264\246\230\246u`-\004\314\031\371\354\353\234j5>\241p\004\310\202s\331\311\213\327\232\003pR\021"
  status: 0
  isRetrying: false
  isUsingSystemPairing: false
  state: 0
}

DEBUG: << Receive (Data=b20108222000ba02aa010a9f010578f722aa64b6a12b09b1695f9ee37a0211cc6ef2f5313ec6174c52ffaed354a3da8e11c52d3aeeb1dd2338a07c3f3c441c3ee1d2d6992a5fbcb6940bd4ac1f98b758dfe28fe916237ea3f448a86df9fab34c83d5aa7e48c0ec7a4ad320d94130f30631b7326aa4d41d445843b68d76c0c14eda48f90ed7b4d40601020320e3987172b7013a57223643b26a0f7093e47f27f2f18acdd19b422c18688ce13d1000180020002800)
DEBUG: << Receive: Protobuf=type: CRYPTO_PAIRING_MESSAGE
priority: 0
[cryptoPairingMessage] {
  pairingData: "\005x\367\"\252d\266\241+\t\261i_\236\343z\002\021\314n\362\3651>\306\027LR\377\256\323T\243\332\216\021\305-:\356\261\335#8\240|?<D\034>\341\322\326\231*_\274\266\224\013\324\254\037\230\267X\337\342\217\351\026#~\243\364H\250m\371\372\263L\203\325\252~H\300\354zJ\323 \331A0\363\0061\2672j\244\324\035DXC\266\215v\300\301N\332H\371\016\327\264\324\006\001\002\003 \343\230qr\267\001:W\"6C\262j\017p\223\344\177\'\362\361\212\315\321\233B,\030h\214\341="
  status: 0
  isRetrying: false
  isUsingSystemPairing: false
  state: 0
}

DEBUG: Device (Encrypted=f722aa64b6a12b09b1695f9ee37a0211cc6ef2f5313ec6174c52ffaed354a3da8e11c52d3aeeb1dd2338a07c3f3c441c3ee1d2d6992a5fbcb6940bd4ac1f98b758dfe28fe916237ea3f448a86df9fab34c83d5aa7e48c0ec7a4ad320d94130f30631b7326aa4d41d445843b68d76c0c14eda48f90ed7b4d4, Public=1f99e4157577c707f24d3f2f4cc563e77420ff267d1b00442bf16189ccbc4d9a)
DEBUG: >> Send (Data=08222000ba0287010a7d0578c348daa95027047d8afb0bbcbe625757b2b54bea555c78b401e8d5ea38083a1c7f5f93af9a704c7178493637bf0e23a865e311ec8f77ec6f8b508731985b58bd34a17077104e83c2ca2e0c84adacc67038e2cf8eaea85641345474f8941eddb3d421fd66b1003b35caf4e32681baec4699a388f2183df31f0601031000180020002800)
DEBUG: >> Send: Protobuf=type: CRYPTO_PAIRING_MESSAGE
priority: 0
[cryptoPairingMessage] {
  pairingData: "\005x\303H\332\251P\'\004}\212\373\013\274\276bWW\262\265K\352U\\x\264\001\350\325\3528\010:\034\177_\223\257\232pLqxI67\277\016#\250e\343\021\354\217w\354o\213P\2071\230[X\2754\241pw\020N\203\302\312.\014\204\255\254\306p8\342\317\216\256\250VA4Tt\370\224\036\335\263\324!\375f\261\000;5\312\364\343&\201\272\354F\231\243\210\362\030=\363\037\006\001\003"
  status: 0
  isRetrying: false
  isUsingSystemPairing: false
  state: 0
}

DEBUG: << Receive (Data=1408222000ba020d0a030601041000180020002800)
DEBUG: << Receive: Protobuf=type: CRYPTO_PAIRING_MESSAGE
priority: 0
[cryptoPairingMessage] {
  pairingData: "\006\001\004"
  status: 0
  isRetrying: false
  isUsingSystemPairing: false
  state: 0
}

DEBUG: Keys (Input=d4da3c08611cbf91e176d435ea39029dea355f202976a2843b36ed1584665d2e, Output=d5ba757d3153e99024793117dfb8ad1e5dd64e06f77e462dec15ec5d71f658fc)
DEBUG: >> Send (Data=08262000d202020802)
DEBUG: >> Send (Encrypted=87eb02ba956db72fb5a393dfe5645381c8a2698981e5098a5e)
DEBUG: >> Send: Protobuf=type: SET_CONNECTION_STATE_MESSAGE
priority: 0
[setConnectionStateMessage] {
  state: Connected
}

DEBUG: >> Send (Data=08102000aa01080801100018002001)
DEBUG: >> Send (Encrypted=9ae28593076e568e7956e21d813fe0b4910dfcd87a8cc6b4c1bf79d0161841)
DEBUG: >> Send: Protobuf=type: CLIENT_UPDATES_CONFIG_MESSAGE
priority: 0
[clientUpdatesConfigMessage] {
  artworkUpdates: true
  nowPlayingUpdates: false
  volumeUpdates: false
  keyboardUpdates: true
}

DEBUG: >> Send (Data=0818122430396632366331662d326566322d343637372d393930372d3038323662653939333364662000)
DEBUG: >> Send (Encrypted=6e688b08b99e457e86d1841ee235e46c58f0a46a236c11d01ae5a99527f3d481a4b30ec6551f6c6745be71aa26f1fcbbd675615a4631c1a66c6a)
DEBUG: >> Send: Protobuf=type: GET_KEYBOARD_SESSION_MESSAGE
identifier: "09f26c1f-2ef2-4677-9907-0826be9933df"
priority: 0

DEBUG: << Receive (Data=8b01c7a6efaa67b256d571cccc3e1e8321092f442c5b6cd296321d54b35d3ca1b74371627bc58b17da0ee4897b0763417f5620f646af9bbc47c4e561df39ca1ff2464d0bd44be7524e2ae05bb76deff4936d39cbbfe41a99df7bb65d01e39fea4b203d9c6dcf5680e486bd10f826694fc922902b79d1e3bcec53fbfd3ba27ca608f8a999309f8b398305c7df783df8817aa8b24d16dbdc67913156eb14cf97e515ea4dea835a0fae7f17a0b59e632ee9bc923e7b802e684a7021869334b690b50b6b48ca78c81db703d83deb02d3cac36e4a94d1a0cd53d2d3240bf9ee46c8da12582d356935eb7247b7f0f6dc42c96e50c49f93c2fb929ba96280fe934d5e2bd2afaa37f5531b8a4133e31f67c081c5106cb068a8685c7a04956f79f30b1bf33dc4c6cca3478af5ddfa967c8c80adf4338f83ac0c96c44af299e31ae675f7e1c292c879120b81c5f040ded3a06a701d2e813ddfe7660c373093642c1939ea459a27e6e74decf9e62d3686aa88c5cb0b4a8ebf0cee9c5b579b76cb40874924fcd6b69f59212317020879efdd367af8e333d2311548fd6ede1eff734fb6b7545e7fea8e64abf1f8ba53e392e51fb47c83e9e5fc4d53d161f94dc4ec7f3609e83f0dfcd95a8dff7502c85ccd32233c913114ca01c4c2718cabbff1177363d715160e231e99987c349b97db6cf6f3fa037516f4e3933b3787c4d462e31d072419f7096d7c176742a080eb6dc9f7a460c607a10df7d52e0643a658e3c8da694e46345e7327dbf765e2371cfb12b9f4fd00fd81d7b87d25f58523c8018dd2b84e4071ef3444fc7a8cc363c848f19dae4fddcafea7c9a515d80f971e0f14ca1cd601f2a2f75674b9270c3b2075ace9e56b7c78a565c4f6383a343fa615462aff697d9760fd3a40cd5d23a3ffc424fdb2172f6d0b65741a4eb2e240ccb90d46ce9c6992d953c56c797e9508a3f39b4a1d61c45c79cf21c9692526216da2675313ec59d4b46a0fa1fbee3036ec95efa0e9b0473f66441fb5d2c6b5b1eb2142426892e03c527b455be07657f0daee25ad5c31ceda1b881e948fdcf261fae9b17713a9408fa753)
DEBUG: << Receive (Decrypted=080420004a751a0208002a07416972506c617930004204080110014a550a0c08011202545618cc86bde20412180880011213636f6d2e6170706c652e5456416972506c61791a2b0a194d6564696152656d6f74652d44656661756c74506c61796572120e44656661756c7420506c6179657259e560a078a2eac141)
DEBUG: << Receive: Protobuf=type: SET_STATE_MESSAGE
priority: 0
[setStateMessage] {
  playbackQueue {
    location: 0
  }
  displayName: "AirPlay"
  playbackState: Unknown
  playbackQueueCapabilities {
    requestByRange: true
    requestByIdentifiers: true
  }
  playerPath {
    origin {
      type: Local
      displayName: "TV"
      identifier: 1280262988
    }
    client {
      processIdentifier: 128
      bundleIdentifier: "com.apple.TVAirPlay"
    }
    player {
      identifier: "MediaRemote-DefaultPlayer"
      displayName: "Default Player"
    }
  }
  playbackStateTimestamp: 601179377.252957
}

DEBUG: Dispatching message with type 4 (SetStateMessage) to Listener(func=<bound method PlayerStateManager._handle_set_state of <pyatv.mrp.player_state.PlayerStateManager object at 0x755c5530>>, data=None)
DEBUG: << Receive (Decrypted=08372000da03260a240880011213636f6d2e6170706c652e5456416972506c617920f5033a07416972506c6179)
DEBUG: << Receive: Protobuf=type: UPDATE_CLIENT_MESSAGE
priority: 0
[updateClientMessage] {
  client {
    processIdentifier: 128
    bundleIdentifier: "com.apple.TVAirPlay"
    processUserIdentifier: 501
    displayName: "AirPlay"
  }
}

DEBUG: << Receive (Decrypted=08482000da04d30212bd020a04080110010a04083a10010acf0108301001880107880103880106880102880105880101880108920123636f6d2e6170706c652e6d757369632e706c61796261636b71756575652e726164696f920127636f6d2e6170706c652e6d757369632e706c61796261636b71756575652e747261636b6c69737492013b636f6d2e6170706c652e4d65646961506c61796261636b436f72652e706c61796261636b436f6e74657874417263686976652d76312e6f7061636b920125636f6d2e6170706c652e6d65646961706c617965722e706c61796261636b636f6e746578740a5d083b1001ba0156636f6d2e6170706c652e4d65646961506c61796261636b436f72652e706c61796261636b53657373696f6e2d76332e6f7061636b3a737562736372697074696f6e3a636c6f75644c6962726172793a383534393361662211636f6d2e6170706c652e54564d75736963)
DEBUG: << Receive: Protobuf=priority: 0

DEBUG: << Receive (Decrypted=08482000da04b0011299010a04080110010a13083010018801038801068801028801058801010a7c083b1001ba0139636f6d2e6170706c652e706f6463617374732e4d54506c61796261636b5175657565436f6e74726f6c6c65722e47656e657269635175657565c20139636f6d2e6170706c652e706f6463617374732e4d54506c61796261636b5175657565436f6e74726f6c6c65722e47656e6572696351756575652212636f6d2e6170706c652e706f646361737473)
DEBUG: << Receive: Protobuf=priority: 0

DEBUG: << Receive (Data=51ce03814e25a5bbaae9592617e4b9b84a7768662ab70b29fd8f0ba2c3f0d6f9bff5485f32ba80957af06a4aa9db2ff3a2b3b1bb42c85d4eca097e4f7e502ec02229f6c9301f9dd037bb11cfb3c49a983653)
DEBUG: << Receive (Decrypted=0817122430396632366331662d326566322d343637372d393930372d3038323662653939333364662000e2011408001a101a0e0800100018003000380040004800)
DEBUG: << Receive: Protobuf=type: KEYBOARD_MESSAGE
identifier: "09f26c1f-2ef2-4677-9907-0826be9933df"
priority: 0
[keyboardMessage] {
  state: 0
  attributes {
    inputTraits {
      autocapitalizationType: NONE
      keyboardType: KEYBOARD_TYPE_DEFAULT
      returnKeyType: RETURN_KEY_DEFAULT
      enablesReturnKeyAutomatically: false
      secureTextEntry: false
      validTextRangeLocation: 0
      validTextRangeLength: 0
    }
  }
}

DEBUG: Loaded AirPlay credentials: 7922D25D1358D91D:049104E8AF6B746CAF33728ED0750048F0D0D10A40E23DE6EAA4C56E35E6AA92
DEBUG: Authentication keys (Private=049104e8af6b746caf33728ed0750048f0d0d10a40e23de6eaa4c56e35e6aa92, Public=83d3de20712969277f47fc1620fe4e4a8701853b6df70517a02bb856189b8a55)
DEBUG: Verification keys (Private=009104e8af6b746caf33728ed0750048f0d0d10a40e23de6eaa4c56e35e6aa52, Public=8b423973155a1286e5878f7ffa0bcf80115c480b7710c073ff708662198c3d51)
DEBUG: POST URL: http://10.0.11.1:7000/pair-verify
DEBUG: -> Data[68]: b'010000008b423973155a1286e5878f7ffa0bcf80115c480b7710c073ff708662198c3d5183d3de20712969277f47fc1620fe4e4a8701853b6df70517a02bb856189b8a55'
DEBUG: <- Data[96]: b'e092d5689a343e7f53be9fcbb8a4386d958f385512d3f954d47ff5945f7fdc1514c79cdf736cd5af5a859dadf5d109ca3902ae5bc2249cd58339af1527820485751bfbfe43919cbfd0c247ce38cb49bd3eb7848449c1de001847d0912889a615'
DEBUG: Verify (Data=14c79cdf736cd5af5a859dadf5d109ca3902ae5bc2249cd58339af1527820485751bfbfe43919cbfd0c247ce38cb49bd3eb7848449c1de001847d0912889a615, PublicSecret=e092d5689a343e7f53be9fcbb8a4386d958f385512d3f954d47ff5945f7fdc15)
DEBUG: Shared secret (Secret=6d8781dac1bd050b6d680554a953e23121c15bcb4d470ed76642d988701f4879)
DEBUG: Pair-Verify-AES (IV=5d481e6283fffd33a1d82a1e6b92510c, Key=d0b90c88502ee59bb5838d1499a84f90)
DEBUG: Signature (Signature=cd6386b9e46164bd26d70568b84805448fdcd75fe49caad749194520d3a65d1f668c0ae609055141469c29e296c30af6cd13f60802191324e910e34d766208a5)
DEBUG: POST URL: http://10.0.11.1:7000/pair-verify
DEBUG: -> Data[68]: b'00000000cd6386b9e46164bd26d70568b84805448fdcd75fe49caad749194520d3a65d1f668c0ae609055141469c29e296c30af6cd13f60802191324e910e34d766208a5'
DEBUG: POST URL: http://10.0.11.1:7000/play
DEBUG: -> Data[177]: b'62706c6973743030d30102030405065f1010436f6e74656e742d4c6f636174696f6e5e53746172742d506f736974696f6e5f1012582d4170706c652d53657373696f6e2d49445f1018727473703a2f2f616c7068612e6c6f63616c3a383838382f10005f102434323230643065612d323265322d343539362d613931372d6436'...
DEBUG: GET URL: http://10.0.11.1:7000/playback-info
DEBUG: << Receive (Data=a5014b667d7c67d1e0eecf39187b8cfd1472dfbd334316f90c97a4282d1c44a92db946dd22c1e5c3fb020ba6bba2471d2a3bcb6d51b6e08d38c9aa1b7f57ea0d081aa621dbf3b8af0db15e73aa1e13480202a46f1bfa6c913d7c3a3a7a0b0ce054c87ec0cecfd1714362a0b1bc838a79cc52f7b08585b939d00500b1340da2a42746ed8e7aff863e72f72399e99fb40aae2327a1aafdf96fb335ba9fad8fbeac86e68fca166739)
DEBUG: << Receive (Decrypted=080420004a8e011a35080012310a2443423234354635362d423845412d343930422d393536372d43413832363943354544444112096100000000000000004a550a0c08011202545618cc86bde20412180880011213636f6d2e6170706c652e5456416972506c61791a2b0a194d6564696152656d6f74652d44656661756c74506c61796572120e44656661756c7420506c61796572)
DEBUG: << Receive: Protobuf=type: SET_STATE_MESSAGE
priority: 0
[setStateMessage] {
  playbackQueue {
    location: 0
    contentItems {
      identifier: "CB245F56-B8EA-490B-9567-CA8269C5EDDA"
      metadata {
        releaseDate: 0.0
      }
    }
  }
  playerPath {
    origin {
      type: Local
      displayName: "TV"
      identifier: 1280262988
    }
    client {
      processIdentifier: 128
      bundleIdentifier: "com.apple.TVAirPlay"
    }
    player {
      identifier: "MediaRemote-DefaultPlayer"
      displayName: "Default Player"
    }
  }
}

DEBUG: Dispatching message with type 4 (SetStateMessage) to Listener(func=<bound method PlayerStateManager._handle_set_state of <pyatv.mrp.player_state.PlayerStateManager object at 0x755c5530>>, data=None)
DEBUG: Playback-info (200): b'<?xml version="1.0" encoding="UTF-8"?>\n<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">\n<plist version="1.0">\n<dict/>\n</plist>\n'
DEBUG: GET URL: http://10.0.11.1:7000/playback-info
DEBUG: Playback-info (200): b'<?xml version="1.0" encoding="UTF-8"?>\n<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">\n<plist version="1.0">\n<dict/>\n</plist>\n'
DEBUG: GET URL: http://10.0.11.1:7000/playback-info
DEBUG: Playback-info (200): b'<?xml version="1.0" encoding="UTF-8"?>\n<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">\n<plist version="1.0">\n<dict/>\n</plist>\n'
DEBUG: GET URL: http://10.0.11.1:7000/playback-info
DEBUG: Playback-info (200): b'<?xml version="1.0" encoding="UTF-8"?>\n<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">\n<plist version="1.0">\n<dict/>\n</plist>\n'
DEBUG: GET URL: http://10.0.11.1:7000/playback-info
DEBUG: Playback-info (200): b'<?xml version="1.0" encoding="UTF-8"?>\n<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">\n<plist version="1.0">\n<dict/>\n</plist>\n'
DEBUG: GET URL: http://10.0.11.1:7000/playback-info
DEBUG: Playback-info (200): b'<?xml version="1.0" encoding="UTF-8"?>\n<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">\n<plist version="1.0">\n<dict/>\n</plist>\n'
DEBUG: media playback ended
DEBUG: Disconnected from device: None
DEBUG: Connection was closed properly
postlund commented 4 years ago

By just glancing at the output, the Apple TV doesn't seem to recognize the stream and start playing it. It is really hard to debug as no feedback/error message is provided back, so can't really tell why. But, my guess is that RTSP is not supported via this method. It is a different subset of AirPlay, as documented here:

https://nto.github.io/AirPlay.html#audio

I guess that MP4 video is basically the safest bet. Can you transcode the audio and stream it via an MP4 container instead? Maybe there's a dummy video generator that produces a black image or so?

ashald commented 4 years ago

Thanks for a super quick reply! Tried with it with mp4 with mp3 and AAC codecs and neither worked. I guess I'll have to abandon this idea for now. Thanks for the help!

postlund commented 4 years ago

No problems! I know that it is quite picky when it comes to how media is served. When I was prototyping #95 ages ago, serving a demo clip (buck bunny IIRC) using the aiohttp webserver didn't work but the exact same clip worked when served from an apache webserver. So I guess support for something was missing but I don't know what. Maybe that can be a lead somewhere?

postlund commented 4 years ago

Been reading up a bit and I don't think RTSP is supported this way. MP4 (even MP3) and live http streaming should work fine. But for RTSP I do believe the RTSP part of AirPlay must be used:

https://emanuelecozzi.net/docs/airplay2/rtsp

It looks like Apple has made some changes to it (why wouldn't they...) so most likely the audio must be relayed via the player, i.e. pyatv in this case. This would be a lot of work, which means something I won't support for now.

postlund commented 4 years ago

Can't really drop this, since the format limitation is irritating. One thing I was thinking about was to do something similar to how I implemented support for playing local files, I.e. spinning up a local web server within pyatv and serve the file from there. In this case I would use ffmpeg and transcode whatever input is given to something that the Apple TV accepts (and point the Apple TV to play from the ffmpeg server). This will of course introduce additional CPU load, additional latency, complexity and an endless amount of parameters that people would want to fiddle with. But it could be a step forward towards support of many more formats and I would probably make it opt-in to use. There are some python bindings for ffmpeg, not sure if they support what I need, but could be investigated.

How would you feel about such a solution?

ashald commented 4 years ago

That sounds great to me - I mean, given all the circumstances!

For now, I found a workaround to use forked-daapd to stream the audio but the connection is dropped sometimes and I had to put around crunches to restart the service and reconnect each time I detect it's down. As I mentioned originally, in nutshell, the use-case here is to stream audio, similarly to how one can stream video with pyatv. From your explanations, my understanding is that one can assemble a solution based on ffmepg and realtime transcoding, but it would've been amazing if pyatv would've supported this out of the box.

postlund commented 4 years ago

I am certainly not an ffmpeg expert, but I do know that you can do a lot of crazy voodoo magic with it. So it should certainly be possible. This page has a lot of examples:

https://trac.ffmpeg.org/wiki/StreamingGuide

In the end I would need something that works with both audio and video. Since ffmpeg has to be installed separately, some nice handling of when it's missing needs to be in place.

My goal would be to make it as simple to stream anything that ffmpeg can transcode as what is natively supported. So it should be transparent to the end user. If you have some time over and feel like experimenting, it would help a lot to have a working example, I.e. an ffmpeg command that does the said thing, which works to stream to the Apple TV by manually inputting a URL to the ffmpeg server.

postlund commented 3 years ago

@ashald Just wanted to check if this is still relevant for you? Since it's possible to stream audio via RAOP/AirTunes now, maybe I can make some modifications to allow a stream from stdin. In that case, you should be able to take in your stream via cvlc, ffmpeg or anything else and just pipe it to atvremote and have it play.

postlund commented 3 years ago

@ashald So, I prototyped this a bit yesterday and I think I have something that can play audio from a buffer, e.g. stdin. So you can pass audio like this (assuming the audio doesn't require seeking, see documentation in PR):

$  ffmpeg -i sample.wav -f mp3 - | atvremote -s 10.0.10.194 --debug stream_file=-

It's of course possible to write a small script that does this, without having to call ffmpeg manually. But this is the easiest most general way. If you have some time to try it out, that would be great!

postlund commented 3 years ago

I had to make a minor fix to get timing right (it was a bug), but now I'm able to stream and re-code audio via RTSP in ffmpeg and pass it to atvremote like this (verified with AirPort Express and HomePod Mini):

$ ffmpeg -i rtsp://wowzaec2demo.streamlock.net/vod/mp4:BigBuckBunny_115k.mov -f mp3 - | atvremote --debug -s 10.0.10.194 stream_file=-

And it plays nicely! It is however worth noting that you get a buffer delay from ffmpeg and additionally about 1,5s delay from AirPlay, so it might take some time until there's output.

postlund commented 3 years ago

I noticed that if the source blocks, e.g. due to buffering, then there will be artifacts in the output. That's logical since we don't send any packets when we are expected to. So, I need to put a small buffer into module dealing with streaming the audio to fix this. The idea is to maintain a small buffer that is immediately available to grab data from when reading frames. A background task will be responsible for filling the buffer when it's getting low. This will allow some minor hiccups without artifacts. If the source blocks long enough so that we run out of buffered frames, then there will be silence and the audio will continue once there are more frames available.

ashald commented 3 years ago

Hi @postlund - thanks for looking into this! I ended up setting up forked-daapd to solve my problem, but what you described above is insanely cool. If I were re-doing my setup from scratch I'd definitely use atvremote instead for its simplicity.

postlund commented 3 years ago

@ashald Great that you managed to get it working! Owntone (formerly forked-daapd) is cool project and works very we'll once it's up and running! So stick with it 😊

I'll try to finish up my buffering implementation by next week and merge it, it's interesting to work with as I have never touched anything remotely close to streaming in any way. So I'm learning a lot!