VodkaTV kiegészítő Kodira

Emlékszem még az időkre, amikor külön extraként tüntették fel a motelekben a kábeltévé előfizetést, vagy amikor az emberek összegyűltek a helyi pubba egy-egy fontosabb sportesemény kapcsán, hogy egy emberként szurkolhassanak közösen, mivel csak nekik volt a környéken ilyen sportcsatornájuk. :)

--- Disclaimer ---

Ezen írás tartalma jogi szempontból tekintve megegyezik egy álom valóságtartalmával. A leírtak csupán álomképek, azokért, illetve azok igazságtartalmáért és az esetleges következményekért felelősséget nem vállalok. Csak rajtad áll, hogy mit kezdessz vele.

--- / Disclaimer ---

Bár talán ezen dolgok egyike sem történelem még, a TV rengeteget változott azóta. Ma már inkább a szabad médiatárak népszerűek és nem a lineáris műsorszórás. Ennek ellenére emlékszem a kezdeti izgalomra, amikor először próbáltam ki a Telekom TVGO platformját az akkori telefonomon és lenyűgözött az ötlet, hogy a telefonomon is tévézhetek. Ezzel párhuzamosan sorra jelentek meg Magyarországon is a különféle szolgáltatók OTT platformjai. Azonban a kezdeti örömöm a TVGO kapcsán nem tartott sokáig. Hamar rájöttem, hogy számomra sok értelme nincs a kiegészítő szolgáltatásuknak. Ha TV-zni akarok, a kis képernyő sosem lesz optimális. Ennek ellenére néha jól jött volna, de mindig volt vele valami nyűg. Vagy azt állította, hogy rootolt a telefonom, amikor nem volt az, vagy csúszott a hang, vagy egyszerűen ezerrel pörgette a CPU-t a különböző obfuszkációk alkalmazása miatt.

Így a TVGO-ról hamar lemondtam, de a többi hazai platform - amihez volt épp hozzáférésem - nyitottabbnak bizonyult arra, hogy tanulmányozzam. A tanulásra pedig remek környezetnek bizonyult a Kodi, ami egy rendkívül sokoldalú médialejátszó alkalmazás és többek között rendelkezik DRM implementációval is, tehát kiválóan alkalmas legális kiegészítők gyártására adott platformokra, ami egy bevett gyakorlattá vált a felhasználói táborban. :)

Hirdetés

Anno volt Vodafone TV előfizetésem, így volt szerencsém azt is kipróbálni, azonban akkoriban a platform igencsak zártnak bizonyult, Linuxon például nem működött, így hanyagoltam. Azt viszont megállapítottam, hogy Kalturát használnak, akik többek között pont OTT platformok fejlesztésével foglalkoznak és sok szolgáltató használja az ő terméküket. Nemrég megjelent a Yettel TV is, amely szintén a Kalturát választotta, azonban ez működik linuxon is, így érdekelt, hogy tudok-e belőle kiegészítőt faragni. Szerencsére sikerült befizetnem a szolgáltatásra, majd el is készült hozzá a nem hivatalos NotYet kiegészítőm, amivel tényleg rengeteget tanultam a platformról, illetve az Oauth bejelentkezés általuk használt módjáról.

Úgy döntöttem, hogy ha a Yettel TV-s tapasztalataim ilyen pozitívak voltak, akkor adok egy esélyt a Vodafone TV-nek ismét, hiszen a platform nagyon hasonló. Tettem vele egy próbát és megannyi trükköt felfedezve sikerült működésre bírni. Igaz, hogy csupán szigorú feltételek mellett és csak bizonyos eszközökön működik Kodi alól, de a lényeg, hogy működőképes a koncepció. Azonban a jelenlegi formájában erősen alpha. Nem is terveztem kirakni például az MSRepoba, mivel jelenleg 'csak' a bejelentkezést tudja, illetve az élő csatornalista megtekintését. Nincs EPG, felvételek kezelése stb. Viszont ha már foglalkoztam vele, a forrást elérhetővé tettem itt.

Elképzelhető, hogy igény esetén foglalkozom még vele és kidolgozom hasonlóra, mint a NotYet-et, de ez a közeljövőben sajnos nem fog megtörténni, az idő és motiváció hiánya miatt. Nagyon macerás fejleszteni így, hogy nem tudom Linuxon tesztelni és mindig telefonra kell betölteni, majd kiszedni a logokat... Ezért döntöttem úgy, hogy kiadom így, ahogy van. De a kiegészítő neheze talán a login és mivel ez megvan nagyjából, innentől könnyű lesz új funkciókat implementálni.

Mivel az egész ennyire kezdetleges, eredetileg nem is terveztem feltenni telepíthető zipként, hogy csak fejlesztők telepítsék, de úgy döntöttem, hogy inkább kiadok egy zipet, nehogy elkezdjen terjedni valami vírusos verzió. Itt elérhető: [link] Kérem, hogy ne terjessze senki más módokon. A Github linkkel bátran terjeszthető.

Node ennyit a bevezetőről, következzen egy kis kitérő a technikai fejlesztéseim technikai hátteréről! Hátha valaki kedvet kap foglalkozni vele és segíthetnek az általam összeszedett gondolatok.

Mindig kutatómunkával szoktam kezdeni a fejlesztéseket, így első körben a Google-t és a VodaTV platformot felhasználóként végignyomkodva igyekeztem számomra érdekesnek tűnő infókat összeszedni.

Ebből nagyjából a következő következtetésekre jutottam:
- a platform Kaltura alapú
- böngészőben widevine, vagy playready van használva nagra licenc szerverrel
- telefonon a nagra saját DRM megoldása lehet használva, mert az OEMCrypto API szerint nem volt egyetlen aktív Widevine folyamat sem aktív lejátszás közben
- A Kaltura platform és a médiák transzkódolása az Amazon platformján történik
- a CDN viszont már Vodafone (feltehetőleg így olcsóbb a sávszél, vagy jobb a peering a Voda előfizetők számára)
- Linuxon nem megy a lejátszás
- bizonyos hálózatokon egyáltalán nem működik a lejátszás, random WA hibakódot ír
- hasonlít a spanyol Vodafone TV konfigurációjára a magyar
- feltehetőleg az STB is ugyanazt a streamet kapja, mint az online VTV platform bizonyos területeken, mivel a stream linkben szerepel a DASH_STB_NGRSSP_LIVE_HD, mint device profile

Ezek után elővettem egy forgalomelemző szoftvert, mivel a reverse engineering legalitása még mindig egy kérdőjel a fejemben. Bár van ahol azt írják (pl [link]), hogy bizonyos körülmények között rendben van (tehát amíg rendeltetésszerűen van használva, nem ad lehetőséget előfizetés nélküli megtekintésre és nem használok kódot/api kulcsokat az eredeti termékből) és én is úgy tudom, hogy az EU-n belül jogomban állna visszafejteni a platformot, biztonságosabbnak tűnt inkább a saját hálózatomat elemezni és az alapján kitalálni a platform működését.

Első körben arra voltam kíváncsi, hogy mehet-e egyáltalán a playback Kodi alól, így a forgalomelemzőből kikerestem, hogy hogyan is történik a lejátszás, majd összeütöttem egy faék egyszerű kiegészítőt, ami megnyitáskor elindítja a beleégetett médiát. A következővel álltam elő:

import xbmcgui
import xbmc
from urllib.parse import urlencode


li = xbmcgui.ListItem('Test')
li.setProperty("inputstream", "inputstream.adaptive")
li.setProperty("inputstream.adaptive.manifest_type", "mpd")
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko)" \
" Chrome/70.0.3538.102 Safari/537.36",
}
li.setProperty("inputstream.adaptive.stream_headers", urlencode(headers))
li.setProperty("inputstream.adaptive.license_type", "com.widevine.alpha")
li.setProperty("inputstream.adaptive.license_key", "https://[...].anycast.nagra.com/[...]/wvls/contentlicenseservice/v1/licenses" +
"|Content-Type=application/octet-stream&nv-authorizations=ey...|R{SSM}|")
li.setMimeType('application/dash+xml')
li.setContentLookup(False)
xbmc.Player().play('http://[...]/wp/[...]/[...]/vxfmt=dp/manifest.mpd?device_profile=DASH_STB_NGRSSP_LIVE_HD', li)

Ezek után telepítettem a kiegészítőt és megpróbáltam elindítani. Nem sikerült, általában csak egy 400-as hibakódot és egy igencsak szűkszavú választ kaptam a licenc szervertől. Pedig nagyon más fejlécet nem követelt meg első ránézésre. Mivel Linux alatt eleve nem ment a dolog böngészőben sem, arra tippeltem, hogy a Linuxos ChromeCDM-et nem engedik. Éppen ezért bebootoltam egy windows gépet, ahol ment böngészőben a lejátszás, majd megpróbáltam telepíteni és futtatni a kiegészítőt. Ugyanúgy 400-at dobott.

Sajnos ez előfordul, mivel van a Widevine-nek egy VMP-re, azaz Verified Media Path-ra keresztelt megoldása. Ez a Windowsos és a MacOS-es CDM sajátja, L3 certekre jellemző. Androidon és Linuxon hiába van L3 cert, itt nincs VMP támogatás. Bár az egész Widevine DRM egy blackbox számomra, a VMP-t úgy képzelem, hogy megnézi a gépen futó szoftveres CDM, hogy milyen alkalmazás indította a DRM kérést és ellenőrzi, hogy az alkalmazás milyen tanusítvánnyal lett aláírva. Ha nem ellenőrzött alkalmazásból indult a kérés, akkor a VMP checken nem megy át a kérés. Ha pedig a platform megköveteli a VMP-t, akkor nem fog menni a lejátszás kodi alól pl, hiszen az az exe fájl a kodi tanúsítványával lett aláírva és nem a Chrome, Firefox, egyedi Electron app, vagy akármiből került lekérésre. Ki akartam zárni, hogy nem-e a VMP hiánya miatt kaptam 400-at, ezért felraktam egy Chromiumot, amit nem a Google írt alá, tehát ezzel biztosan nem megy át a böngészős oldaluk sem a VMP checken. Ha így belépek a hivatalos oldalra és megy a lejátszás, akkor nincs VMP check és más lehet a probléma. Ha viszont nem megy, akkor a VMP lesz a probléma és soha nem fog menni Windowson/Macen L3-mal a támogatott platformokon kívül.

Nagy meglepetésemre remekül ment Chromiumból is a lejátszás. Tehát más a hiba. Nem volt triviális rájönni, de lelövöm a poént. :) Az nv-authorizations headert, ami kell a licenc kéréshez Linux alól bányásztam ki, ahol előtte a Linuxos firefox próbált lejátszást indítani. Ez ugyan 400-zal soha nem sikerült, mivel a Voda nem engedi a Linuxos CDM-et, de úgy tűnik a licenc szerver ettől függetlenül megjegyezte, hogy az eszközöm egy linuxos firefox és nem engedte más eszközről ezzel a tokennel a lejátszást többet. Tehát megismételtem az egész belépést és kiszedtem a tokent, de most windowsos Chrome alól és láss csodát, Kodi alól is elindult a stream. :)

Ezenkívül azt tapasztaltam, hogy L1-es kulcsokkal is működik a playback, illetve SL3000 PlayReady kulcsokkal is, bár a hivatalos alkalmazások közül egyikkel sem találkoztam, amelyik kihasználta volna ezeket. Elképzelhető, hogy nem szándékosan hagyták meg ezek támogatását, és elképzelhető, hogy a közeljövőben bezárják ezt a lehetőséget, de logikus lenne, ha okkal lenne így kialakítva a licenc szerverük, mivel mind az L1, mind az SL3000 kulcsok hardveresen vannak bevédve és ezeket illegális célokra nagyon nehéz felhasználni. Azonban L1 a népszerűbb eszközök közül leginkább Androidos telefonokon, tableteken elérhető, illetve Android TV platformokon, ha fizetett érte a Google-nek a készülék gyártója. Esélyes, hogy olcsó kínai Android boxokon csak L3 lesz, amivel nem fog működni a VTV ilyen módon. Az SL3000 pedig még ennél is ritkább. Android TV-k egy bizonyos részére jellemző. Az én Sony Bravia-m pl rendelkezik vele. Azt viszont le kell szögeznem, hogy a platform üzemeltetői pontosan látják, hogy milyen eszközzel nézted a tartalmat és ha azért bannolnak, mert nem támogatott eszközről nézted a tartalmat, akkor nem én vagyok a felelős. Az is simán elképzelhető, hogy az L1-es 'kiskaput' teljesen lezárják és akkor marad kb a Windows meg a PlayReady (ha marad).

Így tehát a platformok száma limitált, de ezzel együtt kell élni. Androidon a gyári app szoftveres nagrát használ, amit valahogy biztos be lehetne építeni a Kodiba, de ez egy hosszadalmas folyamat lenne, illetve esélyes, hogy nem lehetne legálisan megcsinálni, mivel mindenképp be kéne csomagolni a zárt forráskódú könyvtárukat a kiegészítőbe. Így az olcsó Android alapú android boxok és a raspberry pi támogatás esélyesen ugrott, de ez van.

Szerintem a lejátszást kiveséztük, következhet az eszközazonosító kielemzése. Ez általában egy véletlenszerűen, a kliens oldalon generált egyedi azonosító, amellyel később felregisztrál az eszköz és ezzel tudják majd számon tartani, hogy hány párhuzamos lejátszás, illetve bejelentkezés aktív. Míg a NotYet esetében ezt egy rövidebb, hex karakterekből álló kód adja ki, itt első ránézésre egy UUID4 lehet használva, így a VodkaTV is egy ilyet generál: [link] Ezt letárolja és a belépést követően a session teljes élettartamára ez az azonosító fog az adott belépéshez párosulni. A korábban ecsetelt licenc szerveres eszköz logolás miatt, ha történt már lejátszási próbálkozás egy adott eszközazonosítóról, akkor nem fog mást engedni a backendjük, így ennek a költöztetése, sokszorozása nem lehetséges. Az sem lehetséges, hogyha egyszer PlayReady-vel volt használva, akkor Widevine-re váltson az ember. Ilyen esetekben mindenképp új belépés és eszközazonosító szükséges. Ez fontos! De jobb is így, hiszen csökken a visszaélések lehetősége.

A következő áthidalandó probléma a belépés volt. Kalturánál a NotYet kiegészítő írása közben megtapasztaltam, hogy a login folyamat szolgáltatónként eltérhet, de a végeredmény az, hogy kapunk egy KS tokent/access tokent, aminek segítségével lehet utána csatornalistákat, meg minden mást lekérdezni. itt sincs ez másképp. Tehát ennek a beszerzése lesz az első lépés. Hamar meglett a hálózati logokból az SSOSignIn URL, azonban ez nem csak annyiból áll, hogy elküldjük neki a belépési adataink és visszakapunk egy tokent, vagy egy hibakódot, hogy nem sikerült a belépés, hanem szükségünk van egy API user és jelszó küldésére is, illetve láthatóan a jelszavunk sem nyersen kerül elküldésre. Már az API user és pass problémás, hiszen nem égethetek szolgáltatói API kulcsokat harmadik féltől származó alkalmazásba engedély nélkül. Valahonnan viszont szednie kell az adatokat a webappnak, így megkerestem, hogy hol szerepelnek ezek az értékek. A következő JS fájlt találtam: [link] Innen mindent ki lehet nyerni, ami kell majd a továbblépéshez, tehát tudtam, hogy ennek lekérése lesz az első lépés. Igen ám, de a cikk írásának pillanatában egy configsApp angular objektumot definiál a fentebbi JS. Viszont nekünk ezt Pythonra kéne fordítani. Az első gondolatom az volt, hogy nagyon hasonló az objektum egy JSON objektumhoz, így pythonból a beépített json.loads metódussal be tudnám olvasni. Igen ám, de ahhoz, hogy használható JSON sztringem legyen, dupla idézőjeles kulcsok kellettek volna, ki kellett volna szednem a kommenteket, a listákból az utolsó elemek után bent maradt felesleges vesszőket stb. Egy tucat béna reguláris kifejezéssel sikerült elérni a transzformálást, de hamar lemondtam erről a megoldásról, hiszen semmi sem garantálja, hogy a jövőben nem jön be valami olyan extra paraméter, amit nem kezeltem le megfelelően, és egyébként sem a regex lesz a megfelelő megoldás az átalakításra. Nem is biztonságos, amit én írtam, mert nem kezel le bizonyos eseteket és simán lehetne kártékonyan paramétereket injektálni bele. Erre nyilván kevés az esély Voda oldalról, de semmiképp sem biztonságos. Így ezt a kódot elvetettem és nem használom: [link]

Létezik egyébként pár okosabban megírt külső library a folyamatra, de nem szerettem volna külső függőségeket is csomagolni a kiegészítő mellé. Így egyelőre azt a megoldást választottam, hogy a számomra érdekes értékeket explicit módon próbálom megkeresni és a tartalmukat kiszedni: [link] Itt is regex van használva és ez sem a legbiztonságosabb megoldás, de igyekeztem. Ötletekre egyébként nyitott vagyok...

A következő nagy kérdés a jelszó volt ugyanebben az SSOSignIn kérés adatban. Mint azt korábban említettem, nem plain textként van elküldve a jelszó, hanem base64 kódolású, dekódolva olvashatatlan tartalmú kódot kaptam. Elküldtem egymás után ugyanazt a jelszót kétszer és teljesen más hosszúságú és tartalmú base64 sztringet kaptam. Valami titkosítás lehet tehát és esélyesen nem egy mezei AES-128 CBC például, mivel akkor nem változott volna a tartalom. Elkezdtem tehát RSA kulcsok után kutatni és meg is leltem azt szintén a korábbi config.js kimenetbe égetve. Már csak azt kellett kiderítenem, hogy mégis mivel lehetett az a jelszó titkosítva. Openssl segítségével beolvastam a kulcsot, megnéztem a modulus hosszát és hosszas keresgélés után rátaláltam a PKCS #1 v1.5-re, ami telitalálatnak tűnt. Szerencsére a Python féle PyCryptodome támogatja is: [link] Így ezzel a pár sorral sikerült is egy hasonló sztringet generálnom, amit elküldve sikeresen be tudtam lépni. :) Érdekes egyébként, hogy ezt használják, mivel rákeresve bőven van már utódja az algoritmusnak, nem is ajánlott már a használata Google szerint és támadás is létezik rá. Meg eleve HTTPS a forgalom, tehát elvileg titkosítottan megy a forgalom. Nomeg a titkosított tartalom sem lehet hosszabb, mint a modulus - 11, ami korlátozza a jelszó hosszát, de gondolom jobb, mint a semmi, ezért van bent. A lényeg, hogy viszonylag egyszerűen sikerült megoldani.

Eredetileg egyébként a mobilos API-t szerettem volna használni, mert ott van egy direkt link ehhez a publikus kulcshoz és nem kell JS-ből kinyerni azt, így kevesebb esélye van annak, hogy eltörik a jövőben, de a mobilos API-nál be kellett volna égetni az API usert, amit a fentebb kifejtett okok miatt nem tehetek meg. Illetve az app verziót is folyamatosan növelni kellett volna, különben nem tudok bejelentkezni többet... A webessel elképzelhető, hogy a jövőben változtatnak és 'eltörik' a kiegészítő, de nem találtam jobb megoldást egyelőre.

Ezenkívül a login hívás előtt ki kell deríteni az API elérési útvonalát, meg pár paramétert, amik nem voltak nagyon bonyolult lépések, ezért ezekre nem is térnék ki. Az egyiknél egy XML fájlt kell letölteni és kiszedni a base URL-t, majd ezt a base URL-t megkapva kell lekérni egy getconfig endpointot, ami rengeteg infót visszaad nekünk a lejátszással, meg a belépéssel kapcsolatban. A fontos infókat letárolom, majd elvégzem a bejelentkezést.

Kész a login. Gyorsan leimplementáltam Kodira is a beléptetésnek ezen részét. Már le is tudom kérni a csatornalistát az eddig megszerzett adatokkal. :) Azonban a lejátszáshoz szükséges nv-authorizations tokent és a manifest URL-t nem adja oda a backend, ha nem regisztráljuk az eszközt. Első lépésként áthoztam a NotYet kiegészítőbe fejlesztett teljes eszköz manager kódot, de kisebb módosításokra szorult. Míg Yetteléknél a householddevices/add hívással volt megoldva a regisztráció, itt egy AddDeviceToDomain végű URL tűnt felelősnek a regisztrálásért. Egyszerre talán 5 eszközt enged aktívnak a Voda, viszont a regisztrációra akkor is rápróbál a weboldal, ha betelt az összes slot, nem ellenőrzi előtte, hogy beteltek-e a helyek. Szimplán egy hibakódot kapunk, ha túlléptük a limitet. Ezeket a hibakódokat igyekeztem összeszedni és mind lekezelni. Belefutottam a tesztelések alatt érdekes hibákba, vélhetően azért, mert vannak rate limitek az API-n, ezeket viszont igen nehéz reprodukálni és nem is szeretnék nagyon játszani vele, mivel macerás mindig selfcareből törölni az eszközöket, meg nem is hiszem, hogy örülnének a dolgozók egy API spam-nek, de a tapasztalat az, hogyha ésszerű keretek között használja az ember a kiegészítőt, akkor nem lesz probléma.

Ezenkívül implementáltam még egy KS token frissítő metódust, amit a web is megpróbált megcsinálni, ha például kidobtam az eszközt selfcareből, vagy lejárt a korábbi token, azonban a tapasztalat az, hogy gyakran inkább a teljes újbóli belépésre van szükség.

Illetve bent maradt a kódban a NotYet-ből az eszközök törlésének lehetősége. Ez az opció a hivatalos alkalmazásokban nincs kirakva a GUI-ra, az egyetlen módja az eszközök eltávolításának az általuk adott VTV boxokon lehetséges, illetve a 1270-et tárcsázva. A funkció nem volt kipróbálva, nincs támogatva és a használata nem javasolt anomáliákhoz, esetleg szolgáltatói fiók felfüggesztéséhez, illetve egyéb durva dolgokhoz vezethet. Felelősséget nem vállalok érte és elképzelhető, hogy a jövőben eltávolításra is kerül. Azért maradt csak bent, hogy ne térjen el nagyon a NotYettől a kód és könnyebb legyen karbantartani párhuzamosan a két kiegészítőt. Illetve az implementált metódus a householdból dobja ki az eszközt, nem a domainből, tehát a korábbi AddDeviceToDomain elméletben nem is használható egy householdból kidobott eszköz újbóli felvételére, mivel az még létezhet a domainben. Nomeg a funkció segítségével valószínűleg a szolgáltatói STB-t is ki lehetne dobni, ami nem szerencsés. Mivel viszont az STB-k nincsenek világosan megkülönböztetve az API válaszban és nem feltétlen lehet az eszköz típusára szűrni, mivel sok STB létezik, jelenleg egy csak android telefonokat, tableteket, pc-ket és tv-ket enged kidobni, ha jól emlékszem. Mást nem. Ezt azért építettem bele, hogy nehogy baj legyen.

Megvan tehát a login nagyjából. Tényleg csak apró részletek hiányoznak, amiből a legjelentősebb feladat a rate limitek, illetve egyedi hibakódok kitapasztalása. Dokumentáció hiányában ez pedig rengeteg próbálkozást igényelne, így ezeket fokozatosan lehet majd foltozgatni.

Jöhet a csatornalista. Szerencsére van egy API hívás, amivel lekérhető a teljes lista. Ez nagyon hasonló a NotYetes kivitelezéshez, egy Kaltura filterrel jön vissza az összes objektum. Eddig tiszta. Pár hívás és van egy teljes csatornalistám. Igen ám, de ez tartalmazza az összes létező csatornát, ami elérhető online. Azt is, ami éppen nem része az előfizetett csomagnak. Ki lehetne szűrni azokat a csatornákat, amiket felesleges megjeleníteni, mert nem vagy rá előfizetve, de mivel ez minden csatornalista hívásnál plusz egy API hívást jelentene (le kellene kérni, hogy mi van előfizetve) és mivel a webes felület is megjeleníti a nem előfizetett csatornákat is szürkítve, nem foglalkoztam a szűréssel egyelőre. Minden csatorna megjelenik, s a lejátszásnál kerül majd implementálásra annak kezelése, ha az adott csatorna épp nem lejátszható (mert pl. nem része a csomagnak). Megcsináltam a csatornanevek lekérdezését, kinyertem a csatorna ikonokat és lett is egy szép nagy listám. Még a rendezést is megcsináltam, hogy úgy legyen rendezve a csatornalista, ahogy a weboldal tenné. Igen ám, de eddig túl problémamentes ez a bekezdés. Nyilván itt is akadályba ütköztem, mégpedig hogy a csatorna ikonok nem mindig töltöttek be. Az okát viszont napokig kerestem, mire sikerült rájönni, miért történik. A Voda nagylelkűen visszaad a legtöbb csatornához 2 ikont is. Az egyik egy színes logó, a másik pedig fehér. Én a színes ikonokat szedem ki jelenleg, vagy ami épp előbb van az API válaszban, de a közeljövőben tervezem kapcsolhatóvá tenni. És ezt az ikon linket adom át a Kodinak megjelenítésre. Az megpróbálja letölteni a képet. Igen ám, de ebbe a letöltésbe nem nagyon lehet belenyúlni. A Kodi először küld egy HEAD kérést a linkre, megnézi a Content-Type fejlécet, a hosszat, a válasz kódját és ha mindent jónak talál, akkor intéz egy GET kérést, hogy letöltse a képet. Tegyük meg mi is ezt curl segítségével:

[steve@todo ~]$ curl 'https://[...].images-[...].ott.kaltura.com/Service.svc/GetImage/p/[...]/entry_id/[...]/version/0/width/480/quality/100' -I
HTTP/2 200
content-type: application/json
content-length: 0
[...]

[steve@todo ~]$ curl 'https://[...].images-[...].ott.kaltura.com/Service.svc/GetImage/p/[...]/entry_id/[...]/version/0/width/480/quality/100' -D -
HTTP/2 200
content-type: image/Png
content-length: 16687
[...]

Az első kérés egy HEAD kérés volt, ami egyrészt azt állította, hogy a tartalom, amit ugyanerre a címre GET-elve kapnánk, az egy application/json formátumú adat lenne 0 byte mérettel. A GET kérés viszont azt mondja, hogy image/png a kép és van hossza is. Ismételjük meg a HEAD kérést:

[steve@todo ~]$ curl 'https://[...].images-[...].ott.kaltura.com/Service.svc/GetImage/p/[...]/entry_id/[...]/version/0/width/480/quality/100' -I
HTTP/2 200
content-type: image/Png
content-length: 16687
[...]

Milyen érdekes, most jót ad vissza. Csak találgatni tudok, de valószínűleg nincs gyorsítótárban a HEAD kérésnél a kép és nem is tölti le azt, csak a GET meghívásánál. Utána viszont, mivel már cachelve van, a HEAD is működik egy ideig. Kodiból pedig azért nem jelenik meg a logó, mert ő arra számít, hogy egy 0 byte hosszú JSON-t kapna, így a GET kérést már meg sem csinálja. Hogy működik akkor böngészőből? Merülhet fel jogosan. Nos, ott egy JS kód végzi a betöltést, ami csak egy OPTIONS kérést küld a CORS miatt, majd egyből egy GET történik. Ha szerencsénk van és pont cachelve van az adott logó, amikor lekérnénk, akkor a Kodi meg fogja jeleníteni, egyéb esetben nem.

Erősen bug gyanús viselkedés. Hátha egyszer javítják. Azt nem néztem meg, hogy NotYet esetében tapasztalok-e hasonlót, de ott sose volt gond az ikonokkal.

Gondolkoztam, hogy hogyan lehetne ezt megoldani és a következő ötleteim támadtak:

1. Küldök egy HEAD és opcionálisan egy GET kérést minden egyes képre (ha a HEAD azt mondja, hogy JSON-nal van dolgom), mielőtt átadnám Kodinak listázásra a csatornalistát. Azonban ezzel nagyon hosszú lesz a csatornák listázása, mivel egyszerre egy linket tudok csak letölteni. Megcsinálhatnám töbszálasra a letöltést, vagy aszinkronra, de akkor is az lenne az eredmény, hogy rengeteg adatforgalmat generálnék feleslegesen, hiszen 2x tölteném le a képeket. Egyszer az addon kódjából, egyszer pedig a Kodi töltené le.
2. Csinálhatnék egy helyi media proxy webszervert és ennek a linkjeit adnám át a Kodinak. Így meg tudnám oldani, hogy max egyszer kelljen lekérni a képeket és rendben megjelenjenek. Cachelni is tudnék. Viszont ez plusz egy háttérfolyamat, plusz egy foglalt port a gépen és extra komplexitás.
3. Letölthetném a fájlokat egy helyi elérési útra és ezt adhatnám be a Kodinak. Hátránya, hogy kellene hozzá egy helyi adatbázis, hogy mi van már letöltve, a régi, törölt csatornák logójának törlését le kellene kezelni és az első csatornalista megnyitás lassú lenne, ha sok képet kell letölteni. Meg eleve kérdéses, hogy logókat tárolhatunk-e helyben jogi szempontból...

Szóval ez egyelőre megoldásra vár. Most letölti, ha tudja, ha nem, nem lesznek ikonok. Viszont a Kodi gondoskodik az ikonok cacheléséről, szóval ha egyszer megvannak, vélhetően meg is maradnak.

Van már csatornalistánk. :) Azt hiszed vége? Dehogy, most jön az igazán 'zaftos rész. A lejátszás!

Korábbról begyűjtöttük a lejátszáshoz szükséges összes paramétert. Azt is tudjuk, hogy melyik médiát akarjuk lejátszani, hiszen a user rákattintott a listában. Nincs más dolgunk, mint meghívni a hálózati logokból kibányászott getPlaybackContext endpointot ügyesen felparaméterezve, majd feldolgozni a választ, hibát dobni, ha valami hiba történt, ha meg minden okés, akkor ki lehet szedni a manifest linkjét, az nv-authorizations fejléc tartalmát és át is lehet adni a Kodinak a streamet, nem? Hát nem egészen. Valóban le kell kérni a getPlaybackContext-et. Ez visszaadja az nv tokent, illetve a mainfest linket, ha jobb napja van. Ha nincs, akkor valószínűleg ki lett dobva az eszköz. Azt vettem észre, hogy a böngészős loginokat kilépteti a rendszer egy bizonyos idő után, vagy ha egy újabb eszközzel lépnénk be és már nem lenne szabad slot. Nem tudom pontosan melyik igaz. Viszont sokszor dob olyat, hogy nincs regisztrálva az eszköz. Nem baj, erre az esetre fel tudunk készülni. Ha 1003-at üzen az API, akkor megpróbálunk regelni. Ha nem megy, így jártunk. Törölhető a kiegészítő adatokkal együtt, majd újra be kell lépni. Általában erre nincs szükség és sikeresen megy az újra regisztrációja az eszköznek. Annyi csak a dolog szépséghibája, hogy ha ilyen van, hogy újrareg kell, akkor a user kattint a csatornára, meghívódik a csatorna play metódusa, majd rájön, hogy regisztrálni kell az eszközt. Regisztrálja is, aztán megszakad a folyamat. A user lát egy kis homokórázást, majd visszakerül a csatornalistához. Ismételten kattintva elindul a stream. Meg lehetne csinálni, hogy reg. után próbálkozzon a playbackkel automatikusan, de akkor meg végtelen ciklusba kerülhet a lejátszási próbálkozás. A jövőben célszerű lenne ezt egy számlálóval kikerülni, de egyelőre ilyen luxus nincs az addonba építve. :DDD Együtt kell élni azzal a ritka esettel, hogy néha kétszer kell kattintani.

No, miután lekezeltük ezt az esetet is és gondoltunk arra is, hogy mi van akkor, ha nem fizetett elő a user, vagy az API nem adott vissza linkeket/DRM infót, jöhetne a lejátszás. Mivel megvan már minden, ami kell hozzá. Viszont nálam otthon nem ment a VTV. Se böngészőben a hivatalos appal, se mobilon, se kodiból. Ugyanez viszont vígan ment mobilnetről. Néztem Telekom netről, néztem Vodafone netről, néztem még szerverről is. Egyikről se ment. Mindegyiknél a manifest URL lekérése fulladt kudarcba. Már a curl se tudta letölteni. Na ennek vajon mi lehet az oka? Mutatom!

[steve@todo ~]$ dig live.vtv-prod.vodafone.hu @9.9.9.9 +short
[steve@todo ~]$ dig live.vtv-prod.vodafone.hu @1.1.1.1 +short
8.8.8.8
8.8.4.4
1.2.3.4
4.3.2.1

Hát persze, hogy a DNS! Mi más? :DDD

Tippre arról lehet szó, hogy valami oknál fogva már a DNS-t is tűzfalazzák, hogy milyen IP kérheti le. Ha az épp egy általuk nem szimpatikusnak megítélt DNS szerver IP címe, akkor nem fogja visszaadni az IP címeket a CDN domainhez. Direkt a teszt kedvéért béreltem egy német VPS-t, aminek nem residential IP címe van, s indítottam róla egy DNS kérést közvetlenül az ő szervereikre:

[steve@throwaway ~]$ dig live.vtv-prod.vodafone.hu @ns1.vtv-prod.vodafone.hu +short
[steve@throwaway ~]$

Ugyanez otthoni magyar IP-ről:

[steve@todo ~]$ dig live.vtv-prod.vodafone.hu @ns1.vtv-prod.vodafone.hu +short
8.8.8.8
8.8.4.4
1.2.3.4
4.3.2.1

A Cloudflare, meg Google DNS szerver IP címei meg gondolom whitelistelve vannak, ezért tudják lekérni a címeket. Ami érdekes, hogy van a finneknél is egy VPS-em és ott az 1.1.1.1 sem ad vissza IP címeket. Tehát még a Cloudflare sem teljesen fehérlistás. Szóval ha valaki finnbe megy nyaralni, elképzelhető, hogy a nem geo lockolt csatornákat sem fogja tudni kint nézni (mondjuk nem tudom, van-e ilyen).

Telekom, Vodafone DNS viszont remekül működik vele.

Ez meg így nem pálya, hogy nem lehet olyan DNS-t használni, amilyet szeretnél. Én kifejezetten szeretem a quad9-t, mert kicsivel több privacy-t ígér, mint a társai: [link]. Nem szívesen cserélnék otthon DNS-t, hogy nézhessem azt a szolgáltatást, amire egyébként jogosult lennék és minden technikai feltétel adott lenne. Szóval megcsinálhattam volna, hogy beégetem az IP címeket a kiegészítőbe, vagy hosztolhattam volna saját DNS szervert a kiegészítőnek csak erre a célra, hogy ne kelljen váltani, de ezt megintcsak nem tehetem, hiszen ezek a címek változhatnak, meg jogilag is kérdéseket vetne fel. Arra viszont nyitott voltam, hogy erre az egy szolgáltatásra használjak Cloudflare DNS-t, ha az megy ott, ahol vagyok. DNS kéréseket viszont vagy a DNS protokoll leimplementálásával Pythonba, vagy egy külső libraryvel tudtam volna közvetlenül a Cloudflare felé címezni. Az első megoldás munkás, a második pedig megint egy külső libet igényelne, amit mellőznék. Ezért a következő ötlettel álltam elő. DNS over HTTPS-en kérem le egy egyszerű HTTP üzenet formájában a Cloudflare DNS-től a szerver IP címét, majd visszaadom az első IP-t, ha van ilyen. Ez a workaround csúnya, de működni látszik magyar honból. A finneknél pl ez sem menne, de ez van.

Tudunk tehát IP címet. A következő gond viszont a HTTPS. A manifest link, amit kaptam a getPlaybackContext-től, minden esetben HTTPS volt. HTTPS-nél meg inkább gyakori az, hogy domainre adják és nem IP címre (persze erre is lehetséges kérni). Viszont ha én a https://live.vtv-prod.vodafone.hu-t lecserélem https://8.8.8.8-ra, feltéve, hogy a 8.8.8.8 a live.vtv-prod.vodafone.hu egyik IP címe, akkor a HTTP kliensem panaszkodni fog, hogy a cert nem a 8.8.8.8-ra lett kiadva és elszáll a kérés. Megoldás lehet, ha letiltom az SSL verify-t, de az biztonsági szempontból egy rémálom. Létezik egy megoldás, ami pont ez próbálja megoldani: [link] Ezt beraktam, de így is elhasalt SSL hibával a folyamat. Kiderült, hogy a curl is hasonló módon viselkedik, de az már az eredeti linken is HTTPS-sel meglátogatva. Valahogy a böngészők, mint a firefox és a chrome elfogadják a CDN által használt tanusítványt, de a curl, vagy a pythonos requests már nem. Kézzel letöltve a certet működött, de megint ott vagyok, hogy nem égethetem be más certjét a kiegészítőbe jogi problémák miatt.

Maradt tehát, hogy vagy letiltom a verify-t teljesen, vagy HTTP-n kérem le a mainfestet. Az utóbbit választottam, mert aki akarja, ígyis-úgyis visszaél a helyzettel és MITM-eli a forgalmat. Nem látok nagy veszélyt a dologban, hiszen max mást jelenít meg a Kodid videónak. De mennyi az esély erre az otthoni neteden? :D Persze ezt át kell gondolni a jövőben.

Szóval most kézzel csinál DNS feloldást a kiegészítő, lekéri az IP címről a manifestet. A manifest szerver visszatér... Nade mivel tér vissza? Hát nyilván egy 302 átirányítással. Ez mondjuk teljesen rendben van. Nade ha requestsből tolunk rá egy GET kérést, az a világ végéig nem fog végezni. Nézzünk rá curllal az okára:

[steve@todo ~]$ curl 'http://1.2.3.4/[...]/vxfmt=dp/manifest.mpd?device_profile=DASH_STB_NGRSSP_LIVE_HD' -D - -H 'Host: live.vtv-prod.vodafone.hu' -L
HTTP/1.1 302 Found
Access-Control-Allow-Origin: *
Content-Type: text/html
Date: [...]
Location: http://[...].id.vtv-prod.vodafone.hu/wp/live.vtv-prod.vodafone.hu/[...]/vxfmt=dp/manifest.mpd?device_profile=DASH_STB_NGRSSP_LIVE_HD
Server: [...]
X-Vxpl: modified by d.vtv-prod.vodafone.hu

HTTP/1.1 200 OK
[...]

A jó hír, hogy a curl is úgy viselkedik, mint a böngésző. Megy tovább a redirecttel és kilép szépen. Nade ki látja a problémát? Bingó, nincs Content-Length fejléc a redirectes kérésben, tartalom se jön vissza és a webszerver se zárja a kapcsolatot. Ez pedig arra kényszeríti a pythonos requests modult, meg tucat más programnyelv HTTP könyvtárát is, hogy várjon, míg befut a tartalom. Elvileg ez lenne a szabványkövető magatartás. A curl, firefox és Chrome van olyan értelmes viszont, hogy ha kap egy Location headert, akkor szó nélkül követi azt, nem vár tartalomra. Már a hajadat téped? ;] Én is! Mi ilyen luxust nem tudunk tenni pythonból. El kell hát térnünk a böngészős működéstől és küldenem kell egy HEAD kérést ugyanerre a címre, hiszen a HEAD kérésnek eleve nincs tartalma, így a content-length header is értelmét veszti. így sikeresen ki tudom szedni a manifest URL-t, ami már átadható a Kodinak. Itt már DNS trükközés sem kell egyelőre, ezt a címet eddig minden DNS feloldotta nekem. A HTTPS visszakapcsolása viszont nem játszik itt, mert a cert ugyanúgy nem trusted. Marad tehát a HTTP only streaming egyelőre.

De a lényeg, hogy így végre működik. :C Megy a lejátszás, gyönyörű a kép, nem akad és a hangsávot is lehet váltani. Egyedül a feliratok nem működnek, mert azok raszterizált TTML formában vannak. Erre keresem a megoldást, de esélyes, hogy soha nem lesz megoldható. A PlayReady támogatást is megcsináltam, ami a menüből állítható. A teljes förmedvény, amit összeütöttem a playbackre, itt található: [link]

Azt hiszem kijelenthetem, hogy bőven volt mit tanulni ebből a platformból is. :)

Visszajelzéseket szívesen várok és köszönöm, hogy elolvastad!

PS: köszönet @tompnak és @vargalexnek a VTV tesztelés megkönnyítéséért!

Végezetül pedig következzen egy lista az eddig tesztelt platformokkal, ahol működött a kiegészítő lejátszással együtt:

Android

- Google Pixel 5 (csak L1)
- Xiaomi MI 9T (csak L1)

Android TV

- Sony BRAVIA 4K UR3 (L1 & PlayReady SL3000; mindkettő tökéletes, de PlayReady-n gyorsabb a playback)
- Nvidia SHIELD TV Pro (csak L1; PlayReady nem működött)
- Homatics Box R 4K Plus (L1 & PlayReady SL3000; utóbbi viszont PlayReady-vel gyakran bufferelt, meg-megállt a kép)
- MECOOL KM2 PLUS (L1; PlayReady nem volt tesztelve)
- SDMC TelekomTV STB (csak L1, PlayReady nem működött)
- Chromecast (csak L1; PlayReady nem működött)
- Amazon Fire Tv 4k Max (L1; PlayReady nem volt tesztelve)

Windows

- Windows 10 (L3 ChromeCDM 2557)
- Windows Server 2022 (L3 ChromeCDM 2557)

Linux

- Arch Linux (nem működik wine alól futtatott Kodival sem)

Még van hozzászólás! Tovább