my-radio APIs — how to use them =============================== Two APIs, no UI, both on the same server and both opened with the same key. Stream API api/stream.php YouTube link in, audio out Sync API api/history.php the myCarConnect phones' shared YouTube list and trips Server: https://host4data.cloud/favkalaams/my-radio/ ------------------------------------------------------------------------------------ 0. KEYS (do this once) ------------------------------------------------------------------------------------ Make a key on the server: php -r 'echo bin2hex(random_bytes(20)), "\n";' Put it in api/config.php with a label you will recognise: 'STREAM_API_KEYS' => [ '' => 'Phones', ], - One line per device or app is best: each key gets its own daily allowance, its label shows in data/worker.log, and deleting its line cuts off just that one. - For the Sync API, a key IS an account: every phone using the same key shares the same YouTube list and trips. Give the phones that should share history the same key. - Treat a key like a password. Anyone holding a URL with the key in it can use it. - health.php tells you how many keys are set (never the keys themselves) and warns about any shorter than 20 characters: https://host4data.cloud/favkalaams/my-radio/api/health.php ==================================================================================== 1. STREAM API — api/stream.php ==================================================================================== GET https://host4data.cloud/favkalaams/my-radio/api/stream.php?url=&key= url any YouTube link (watch?v=…, youtu.be/…, shorts/…). No URL-encoding needed. key your key. Or send it as a header instead: X-API-Key: t, to optional: play only a passage, e.g. &t=10m&to=15m (or seconds: &t=600&to=900). Both or neither — a link carrying only ?t= is refused. Answer: audio/mpeg (MP3). Ways to play it VLC (PC) Media -> Open Network Stream -> paste the URL -> Play VLC (Android) More -> New stream -> paste the URL A browser paste the URL into the address bar A web page Save to a file curl -o talk.mp3 "…the URL…" What happens - Already fetched before (by anyone, any way): plays at once, and you can seek. - A passage of something already fetched: cut in a few seconds, then the same. - Never fetched: about 10-15 seconds of silence while the server reaches YouTube, then it plays while it downloads. No seeking during that first play. The download finishes even if you stop listening, so the next play is instant. Limits - 50 NEW videos per key per day (STREAM_DAILY_NEW). Replays are always free, and so is asking for something another listener is already downloading. - Anything fetched through this API is kept forever. Errors come back as JSON {"ok":false,"reason":"…","error":"…"} instead of audio: 400 not a YouTube link, or only one of t / to 401 no key, or a wrong one 403 the video is private, members-only, age-restricted or blocked in this country 404 the video was removed 422 it is a live stream 429 today's allowance is used up (Retry-After says when it resets; replays still work) 502 YouTube refused the server — check yt_proxy in health.php (WARP may need a reconnect) 504 no audio in time — the download carries on; ask again in a minute A video that failed in the last 2 minutes answers with the same error straight away instead of trying YouTube again. Quick test curl -s -o /tmp/s.mp3 -w 'HTTP %{http_code} first byte %{time_starttransfer}s\n' "https://host4data.cloud/favkalaams/my-radio/api/stream.php?url=https://youtu.be/jNQXAC9IVRw&key=KEY" ==================================================================================== 2. SYNC API — api/history.php (the myCarConnect phone apps) ==================================================================================== The phones build this URL from the Stream API's address, with the same key: https://host4data.cloud/favkalaams/my-radio/api/history.php?key= YouTube list https://host4data.cloud/favkalaams/my-radio/api/history.php?key=&list=trips trips - POST with a JSON body (Content-Type: application/json). The answer is always JSON. - GET with the same URL reads what is stored and changes nothing. - history.php?version answers {"version":6} (no key needed). 5 one set of lists per key; routes kept whole 6 trips also keep "times" (see 2b) - Times are Unix milliseconds (UTC). - Errors: wrong or missing key -> 403. Anything else -> a non-2xx status with {"error": "what went wrong"}. The apps keep their own copy and simply try again later. 2a. YouTube list (POST …history.php?key=KEY) Send the phone's whole list and its removals: {"items": [{"id": "mjO5zQD3pbU", "url": "https://www.youtube.com/watch?v=mjO5zQD3pbU", "title": "Zehni Azmaish Season 14 New Kalam", "at": 1791206250726}], "removed": {"uji7l0WgXx4": 1791290000000}} id exactly 11 characters of A-Z a-z 0-9 _ - (anything else is ignored) url ignored on the way in; always rebuilt from the id on the way out title may be empty; a known title is never replaced by an empty one at when it was last played on that phone removed id -> when it was removed on a phone Answer: the merged list from every phone, in the same shape, newest "at" first: {"items": [...], "removed": {...}} ("removed" is {} when empty, never []) Merge rules - per video, the larger "at" wins - removals keep the larger time per video - a video is dropped when its removal time >= its "at": a removal beats older plays, a newer play brings it back - no limit on the number of items; removals are forgotten after 30 days 2b. Trips (POST …history.php?key=KEY&list=trips) Send only what changed on the phone since its last sync, plus all its removals: {"since": 1791285689375, "trips": [{"id": 1791200000000, "startAt": 1791200000000, "endAt": 1791200900000, "distanceM": 7350.5, "startName": "Road 12, Banjara Hills, Hyderabad", "endName": "Charminar, Hyderabad", "destination": "Charminar", "note": "", "tag": "work", "source": "", "updatedAt": 1791200950000, "path": "_p~iF~ps|U_ulLnnqC_mqNvxq`@", "times": "0,312,900"}], "removed": {"1791100000000": 1791286000000}} id number, up to 16 digits (the trip's start time) path the route as a Google encoded polyline. Kept WHOLE up to 2 MB per trip; a longer one is refused rather than cut into a broken route times (version 6) whole seconds after startAt at which the car reached each point of path, comma-separated, one per point, same order: "0,5,9,14,...". So a replay keeps the real pace, stops at signals included. Stored and sent back EXACTLY as received, never parsed or tidied. "" for trips recorded before times were kept, and for any trip stored by version 5 (which dropped the field). Kept whole up to 2 MB (a long trip is about 100 KB); a longer one is refused rather than cut, since a shortened list would no longer match the path source "unit" for a drive the head unit recorded, "" for a phone's updatedAt when the trip was last created or edited on a phone (required) since the "now" from this phone's last answer (0 the first time) Answer: every trip changed on the server since "since" (by any phone, this one included), all removals, and the "now" to send as "since" next time: {"now": 1791285689446, "trips": [{… the fields above, plus "serverAt" …}], "removed": {"1791100000000": 1791286000000}} Merge rules - an incoming trip is stored unless its removal time >= its updatedAt, or the stored copy's updatedAt is >= it; when stored it is stamped serverAt = now - removals keep the larger time per id, and delete the stored trip when they are at least as new as it. (Phones resend all their removals every time, so an OLD removal never deletes a trip that was edited after it.) - "now" always increases — two phones syncing in the same millisecond still get different stamps, so neither misses the other's trip - "Clear" on a phone sends every trip id in "removed", so those trips never come back Size: a request may be up to 32 MB (SYNC_MAX_BODY in config.php). php.ini's post_max_size does NOT limit this API. 2c. Where the data lives data/sync// — youtube.json, and trips/ with one file per trip. data/ is never served by the web server. Nothing else in my-radio reads data/sync/. Set SYNC_DIR in config.php to keep it somewhere else. Moving from the previous history.php: the first sync after the switch copies across everything it stored (data/yt-history.data.php and data/trips.data.php). Trips keep their old serverAt, so phones are not sent everything again. It happens once; the old files are left untouched, as a backup. Quick test (on the server; prints the stored YouTube list) curl -s "https://host4data.cloud/favkalaams/my-radio/api/history.php?key=KEY" ------------------------------------------------------------------------------------ 3. CHECKING ON THINGS ------------------------------------------------------------------------------------ Health page https://host4data.cloud/favkalaams/my-radio/api/health.php Recent activity tail -30 /var/www/html/favkalaams/my-radio/data/worker.log (lines starting "stream:" are the Stream API, "sync:" the Sync API) Test suites php tests/test_stream.php php tests/test_sync.php php tests/test_engine.php php tests/test_diagnostics.php