Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Verify compatibility with tvOS 18 #2403

Open
postlund opened this issue Jun 21, 2024 · 9 comments
Open

Verify compatibility with tvOS 18 #2403

postlund opened this issue Jun 21, 2024 · 9 comments
Labels
investigate Needs more investigation to see if possible

Comments

@postlund
Copy link
Owner

What to investigate?

Upgrade to current beta and try it out.

Expected outcome

See if anything needs to be fixed.

@postlund postlund added the investigate Needs more investigation to see if possible label Jun 21, 2024
@knopp
Copy link

knopp commented Jun 21, 2024

Video playback (play_url) seems to fail with 500 at /GET playback-info. Even when ignoring the error playback doesn't start.

@postlund
Copy link
Owner Author

When playing video or audio or both?

@knopp
Copy link

knopp commented Jun 21, 2024

I tried it with a HLS URL.

atvremote -n "Apple TV 4" --debug play_url=https://devstreaming-cdn.apple.com/videos/streaming/examples/bipbop_4x3/bipbop_4x3_variant.m3u8

@postlund
Copy link
Owner Author

@knopp Could you possibly attach some debug logs I can look at?

@postlund
Copy link
Owner Author

I can confirm the issue now as well, not sure why it happens. Will have to investigate further.

@postlund
Copy link
Owner Author

postlund commented Aug 1, 2024

I suspect that the endpoint I'm using (/playback-info) is no longer available as that is only for AirPlay 1 and that call should not be made anymore. Not sure yet what to do instead though, will dig into that.

@thiccaxe
Copy link
Contributor

thiccaxe commented Aug 4, 2024

If you change the client version in the companion protocol to a i(pad)os18 or higher version, and subscribe to "NowPlayingInfo" events, in some AppleTV+ titles that send over large packets, there is an extraneous null byte inserted before the beginning of the bplist that is encoded as the value of the dictionary key "NowPlayingInfoKey". Removing this null byte allows everything to be decoded properly.

one of more of the following components fail:

  • Opack Decoding
  • Decryption (no errors)

If this takes place during decryption, which I doubt, the null byte would have to be inserted after the decryption takes place, and somewhere along the way from the crypto internals to the api surface. If it happened before, there would be a mismatch between the data length and the header, which would lead to a decryption error, likely. Moreover, the extra null byte doesn't appear at a power-of-2 location, which could be a internal crypto block size (i.e byte 63/64/65, or byte 127/128/129). This all points to the fact that the encryption is fine

This means that it is likely that the opack decoder is not "up to the standard". It could also be obfuscation measures from apple.

If someone with a mac wants to test the opack, or replicate this "bug?", t o obtain the "bad" opacks, take a look at the following code:

import pyatv
import asyncio, binascii
import logging
from datetime import datetime
import plistlib
logging.basicConfig(
    level=logging.DEBUG,
    datefmt="%Y-%m-%d %H:%M:%S",
    format="%(asctime)s %(levelname)s [%(name)s]: %(message)s",
)

async def mrec(message):
    print("received message: ", plistlib.loads(message["NowPlayingInfoKey"]))

async def main():
    loop = asyncio.get_running_loop()
    atv = (await pyatv.scan(loop, hosts=["192.168.1.3"]))[0]

    atv.set_credentials(pyatv.Protocol.Companion, "")
    atv = await pyatv.connect(atv, loop)
    api: pyatv.protocols.companion.CompanionAPI
    print(atv.remote_control._interfaces[pyatv.Protocol.Companion])
    api = atv.remote_control._interfaces[pyatv.Protocol.Companion].api
    await api.subscribe_event("NowPlayingInfo")
    api.listen_to("NowPlayingInfo", mrec)

    await atv.remote_control.skip_forward(float("NaN"))
    while True:
        await asyncio.sleep(3)

    atv.close()
if __name__ == "__main__":
    asyncio.run(main())

In the system info packet in protocols/companion/api.py you will need to change the following to reflect a i—18 client

"_sv": "600.41.1", # Software Version (I guess?)

I suppose the hacky patch is to indexof "bplist00", check if there is a null byte before it, then look back a bit more to see if the bytes 0x92,0x93 or 0x94 are present 2, 3, or 4 bytes behind, which would represent bytes encoded in opack, and then remove that null byte.

@thiccaxe
Copy link
Contributor

thiccaxe commented Aug 7, 2024

I think it makes more sense if we look at 0x91 having 2^0 bytes of length, 0x92 having 2^1 bytes of length, 0x93 having 2^2 bytes of length and 0x94 having 2^3. This would make the extra "00" a part of the length, and would explain its presence

Previously, we encountered no opacks longer than 0xFFFF. so, byte data was done as 0x90, 0x91, or 0x92. Now that we are looking at data which is longer than 0xFFFF, it jumps up to four bytes of length instead of three — its not linear. It should be a very easy patch in the code.

@petro-kushchak
Copy link

petro-kushchak commented Aug 13, 2024

@postlund audio streaming started failing with latest iOS18 beta (and latest pyatv 0.15.0)

Attached logs for

atvremote -n "Studio" --debug play_url=https://devstreaming-cdn.apple.com/videos/streaming/examples/bipbop_4x3/bipbop_4x3_variant.m3u8:

ios18-homepod-pyatv-logs.txt

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
investigate Needs more investigation to see if possible
Projects
None yet
Development

No branches or pull requests

4 participants