diff --git a/Basis/Packages/com.basis.integration.ytdlp/TESTING.md b/Basis/Packages/com.basis.integration.ytdlp/TESTING.md index 4539fe9f2..537fa53d7 100644 --- a/Basis/Packages/com.basis.integration.ytdlp/TESTING.md +++ b/Basis/Packages/com.basis.integration.ytdlp/TESTING.md @@ -41,7 +41,7 @@ from the front page, plus a recent VOD from the same channel. | --- | --- | | YouTube VOD, >360p | Resolves to a **split stream** (capability-selected H.264/VP9/AV1 video-only + AAC or Opus-fallback audio-only), plays paced as on-demand, A/V locked | | YouTube VOD, ≤360p (or format-forced) | Single muxed stream, delivery auto-detected | -| YouTube live | Single HLS playlist, plays as live | +| YouTube live | Single HLS playlist, joining near the live edge. Worth re-running after any HLS change: unlike Twitch, the media playlist lists the entire DVR window — thousands of segments, several MB of text, segment URIs past 1 KB — so it is the lane that exercises the parser's size limits | | Twitch live | HLS live; join near the live edge | | Twitch VOD | HLS VOD | | Format selection, VP9-capable platform without AV1 decode | A 4K upload resolves to the **VP9 video-only rung up to 2160p** (WebM or MP4 carriage) + `mp4a` audio as a split stream; a 1080p-max upload still resolves to `avc1` (avc1 wins at equal height) | diff --git a/Basis/Packages/com.basis.mediaplayer/Native~/basis_media_core.c b/Basis/Packages/com.basis.mediaplayer/Native~/basis_media_core.c index a163b68de..735a2e152 100644 --- a/Basis/Packages/com.basis.mediaplayer/Native~/basis_media_core.c +++ b/Basis/Packages/com.basis.mediaplayer/Native~/basis_media_core.c @@ -1271,11 +1271,16 @@ static basis_media_engine_t* open_impl(const char* url, const char* audio_url, i e->paced_hint = delivery_hint; e->paced = (delivery_hint == 2) ? 1 : 0; e->pace_delivery = e->paced; /* VOD paces delivery; run_hls also enables it for live HLS */ + /* Reject an over-long URL rather than storing a prefix: e->url is what every + * fetch re-sends, and a clipped one is a request the origin refuses with + * nothing to distinguish it from a genuine authorisation failure. */ + if (strlen(url) >= sizeof(e->url)) { free(e); return NULL; } strncpy(e->url, url, sizeof(e->url) - 1); if (basis_url_parse(url, &e->parts) != 0) { free(e); return NULL; } int has_audio = (audio_url && audio_url[0]); if (has_audio) { + if (strlen(audio_url) >= sizeof(e->url_audio)) { free(e); return NULL; } strncpy(e->url_audio, audio_url, sizeof(e->url_audio) - 1); if (basis_url_parse(audio_url, &e->parts_audio) != 0) { free(e); return NULL; } } diff --git a/Basis/Packages/com.basis.mediaplayer/Native~/protocol/basis_hls.c b/Basis/Packages/com.basis.mediaplayer/Native~/protocol/basis_hls.c index eb0d94161..0cccbebd9 100644 --- a/Basis/Packages/com.basis.mediaplayer/Native~/protocol/basis_hls.c +++ b/Basis/Packages/com.basis.mediaplayer/Native~/protocol/basis_hls.c @@ -51,9 +51,13 @@ static void hls_mutex_lock(hls_mutex_t* m) { pthread_mutex_lock(m); } static void hls_mutex_unlock(hls_mutex_t* m) { pthread_mutex_unlock(m); } #endif -#define HLS_MAX_URI 1024 -#define HLS_MAX_ITEMS 512 /* fetchable items collected per playlist parse */ -#define HLS_MAX_PLAYLIST (1 << 20) /* 1 MiB playlist cap */ +/* Signed CDN segment URIs carry the whole authorisation payload in the path and + * run well past 1 KB (YouTube live signs ~1150 chars), so the cap has to clear + * that with room to spare. It bounds the playlist struct, which is heap-allocated + * at both parse sites for that reason. */ +#define HLS_MAX_URI 2048 +#define HLS_MAX_ITEMS 512 /* fetchable items retained per playlist parse */ +#define HLS_MAX_PLAYLIST (8 << 20) /* 8 MiB playlist cap */ #define HLS_MAX_EMPTY_RELOADS 8 /* consecutive no-new-media reloads before giving up */ #define HLS_LIVE_MARGIN_SEGMENTS 3 /* playout buffer kept behind the live edge for plain (non-LL) HLS */ /* Read-ahead byte buffer (~5 s of 1080p HD). Kept small on purpose: a larger @@ -80,7 +84,13 @@ typedef struct { int is_fmp4; /* EXT-X-MAP present or .m4s/.mp4 segment URIs */ char map_uri[HLS_MAX_URI]; /* EXT-X-MAP init segment (fMP4), resolved */ int nfull; /* count of full (#EXTINF) segments */ + /* Retained items, oldest-first from item_start. A DVR live playlist lists far + * more segments than the cap (a YouTube broadcast publishes its whole + * multi-hour window every reload), and the ones worth playing are at the live + * edge, so once full a live playlist rolls the oldest out. VOD keeps the head: + * its playout starts at the first segment. Index via playlist_item(). */ hls_item_t items[HLS_MAX_ITEMS]; + int item_start; int item_count; } hls_playlist_t; @@ -219,33 +229,40 @@ static long attr_ms(const char* line, const char* key, long def) { /* Resolve `ref` against the absolute base URL `base` into `out`. * Handles absolute (http[s]://), root-relative (/path) and same-directory - * relative refs. "../" is not normalised (rare in HLS); acceptable first pass. */ -static void resolve_url(const char* base, const char* ref, char* out, int outsz) { + * relative refs. "../" is not normalised (rare in HLS); acceptable first pass. + * + * Returns 0 when the resolved URL doesn't fit. A relative ref is concatenated + * onto the base, so the result can overrun where neither part alone would, and + * a clipped absolute URL is still well-formed enough to be fetched — callers + * drop the reference rather than store one the origin will refuse. */ +static int resolve_url(const char* base, const char* ref, char* out, int outsz) { + int n; if (starts_with(ref, "http://") || starts_with(ref, "https://")) { - snprintf(out, outsz, "%s", ref); - return; - } - /* find scheme://host end (first '/' after "scheme://") */ - const char* host = strstr(base, "://"); - host = host ? host + 3 : base; - const char* host_end = strchr(host, '/'); - - if (ref[0] == '/') { - if (host_end) { - int hlen = (int)(host - base) + (int)(host_end - host); - snprintf(out, outsz, "%.*s%s", hlen, base, ref); + n = snprintf(out, outsz, "%s", ref); + } else { + /* find scheme://host end (first '/' after "scheme://") */ + const char* host = strstr(base, "://"); + host = host ? host + 3 : base; + const char* host_end = strchr(host, '/'); + + if (ref[0] == '/') { + if (host_end) { + int hlen = (int)(host - base) + (int)(host_end - host); + n = snprintf(out, outsz, "%.*s%s", hlen, base, ref); + } else { + n = snprintf(out, outsz, "%s%s", base, ref); + } } else { - snprintf(out, outsz, "%s%s", base, ref); + /* same-directory relative: base up to and including last '/' (before any '?') */ + const char* q = strchr(base, '?'); + const char* end = q ? q : base + strlen(base); + const char* slash = end; + while (slash > host && *slash != '/') slash--; + int dirlen = (slash >= host && *slash == '/') ? (int)(slash - base) + 1 : (int)(end - base); + n = snprintf(out, outsz, "%.*s%s", dirlen, base, ref); } - return; } - /* same-directory relative: base up to and including last '/' (before any '?') */ - const char* q = strchr(base, '?'); - const char* end = q ? q : base + strlen(base); - const char* slash = end; - while (slash > host && *slash != '/') slash--; - int dirlen = (slash >= host && *slash == '/') ? (int)(slash - base) + 1 : (int)(end - base); - snprintf(out, outsz, "%.*s%s", dirlen, base, ref); + return n >= 0 && n < outsz; } /* ---- playlist fetch ------------------------------------------------------ */ @@ -289,7 +306,11 @@ static int fetch_text(basis_hls_t* h, const char* url, char** out, int* out_bloc for (;;) { if (h->is_running && !h->is_running(h->user)) { free(buf); h->http.close(ctx); return -1; } if (len + 4096 > cap) { - if (cap >= HLS_MAX_PLAYLIST) break; + /* Over the cap: fail rather than return what fitted. A clipped body + * ends mid-URI, and the truncated URI parses as a valid-looking + * segment the CDN then rejects — a silent playback failure that + * looks nothing like the size limit that caused it. */ + if (cap >= HLS_MAX_PLAYLIST) { free(buf); h->http.close(ctx); return -1; } cap *= 2; char* nb = (char*)realloc(buf, cap); if (!nb) { free(buf); h->http.close(ctx); return -1; } @@ -312,6 +333,18 @@ static int playlist_is_master(const char* text) { return strstr(text, "#EXT-X-STREAM-INF") != NULL; } +/* Copy one playlist line (CR stripped) into out. Returns 0 if it doesn't fit — + * callers drop the line rather than store a prefix, since a clipped URI is a + * URL the origin answers 403 to and nothing downstream can tell it apart from a + * genuine authorisation failure. */ +static int copy_line(char* out, int outsz, const char* src, int len) { + if (len >= outsz) return 0; + memcpy(out, src, (size_t)len); + out[len] = 0; + if (len && out[len - 1] == '\r') out[len - 1] = 0; + return 1; +} + /* Pick a single rendition from a master playlist: highest BANDWIDTH variant. * Writes its resolved absolute URL into out. Returns 1 on success. */ static int master_pick_variant(basis_hls_t* h, const char* base, const char* text, @@ -335,12 +368,16 @@ static int master_pick_variant(basis_hls_t* h, const char* base, const char* tex while (*q) { const char* qnl = strchr(q, '\n'); int qlen = qnl ? (int)(qnl - q) : (int)strlen(q); - char uline[HLS_MAX_URI]; - if (qlen >= (int)sizeof(uline)) qlen = (int)sizeof(uline) - 1; - memcpy(uline, q, qlen); uline[qlen] = 0; - if (qlen && uline[qlen - 1] == '\r') uline[qlen - 1] = 0; - if (uline[0] && uline[0] != '#') { - if (bw >= best_bw) { best_bw = bw; resolve_url(base, uline, best_uri, sizeof(best_uri)); } + if (qlen && q[0] != '#' && q[0] != '\r') { + /* A variant URI that doesn't fit is skipped, leaving a + * shorter-URI rendition to win rather than selecting one + * that can't be fetched. */ + char uline[HLS_MAX_URI], cand[HLS_MAX_URI]; + if (copy_line(uline, (int)sizeof(uline), q, qlen) && uline[0] && bw >= best_bw && + resolve_url(base, uline, cand, sizeof(cand))) { + best_bw = bw; + snprintf(best_uri, sizeof(best_uri), "%s", cand); + } break; } if (!qnl) break; @@ -356,13 +393,37 @@ static int master_pick_variant(basis_hls_t* h, const char* base, const char* tex return 1; } +/* The i-th retained item in playlist order. */ +static const hls_item_t* playlist_item(const hls_playlist_t* pl, int i) { + return &pl->items[(pl->item_start + i) % HLS_MAX_ITEMS]; +} + +/* Claim a slot for the next item. Once the array is full a live playlist rolls + * the oldest item out (the live edge is what plays); a VOD keeps what it has. + * Returns NULL when the item was dropped. */ +static hls_item_t* playlist_append(hls_playlist_t* pl, int live) { + if (pl->item_count < HLS_MAX_ITEMS) { + int idx = (pl->item_start + pl->item_count) % HLS_MAX_ITEMS; + pl->item_count++; + return &pl->items[idx]; + } + if (!live) return NULL; + int idx = pl->item_start; + pl->item_start = (pl->item_start + 1) % HLS_MAX_ITEMS; + return &pl->items[idx]; +} + /* Parse a media playlist into pl. base = media playlist URL (for resolving). */ static void parse_media_playlist(const char* base, const char* text, hls_playlist_t* pl) { memset(pl, 0, sizeof(*pl)); pl->media_seq_base = 0; + /* Retention policy is decided up front: EXT-X-ENDLIST closes a VOD, and its + * absence means the list is a live window that will be reloaded. */ + int live = strstr(text, "#EXT-X-ENDLIST") == NULL; + const char* p = text; - char line[2048]; + char line[HLS_MAX_URI + 512]; /* holds a tag line carrying a full-length URI */ int seg_index = 0; /* full segments seen so far */ int part_index = 0; /* parts of the in-progress segment */ @@ -388,19 +449,32 @@ static void parse_media_playlist(const char* base, const char* text, hls_playlis } else if (starts_with(line, "#EXT-X-MAP")) { char uri[HLS_MAX_URI]; if (attr_str(line, "URI", uri, sizeof(uri))) { - resolve_url(base, uri, pl->map_uri, sizeof(pl->map_uri)); + /* An init segment that doesn't fit leaves map_uri empty — the + * same state as a playlist carrying no EXT-X-MAP, and better + * than fetching a clipped URL as though it were the real one. */ + if (!resolve_url(base, uri, pl->map_uri, sizeof(pl->map_uri))) pl->map_uri[0] = 0; pl->is_fmp4 = 1; } } else if (starts_with(line, "#EXT-X-PART:")) { char uri[HLS_MAX_URI]; - if (attr_str(line, "URI", uri, sizeof(uri)) && pl->item_count < HLS_MAX_ITEMS) { - hls_item_t* it = &pl->items[pl->item_count++]; - resolve_url(base, uri, it->uri, sizeof(it->uri)); - it->msn = pl->media_seq_base + seg_index; - it->part = part_index++; - it->dur_ms = attr_ms(line, "DURATION", 0); - pl->has_parts = 1; - if (ends_with_ci(it->uri, ".m4s") || ends_with_ci(it->uri, ".mp4")) pl->is_fmp4 = 1; + if (attr_str(line, "URI", uri, sizeof(uri))) { + /* Resolve before claiming a slot: a slot taken and then + * abandoned would sit in the array with no URI. */ + char resolved[HLS_MAX_URI]; + hls_item_t* it = resolve_url(base, uri, resolved, sizeof(resolved)) + ? playlist_append(pl, live) : NULL; + if (it) { + snprintf(it->uri, sizeof(it->uri), "%s", resolved); + it->msn = pl->media_seq_base + seg_index; + it->part = part_index; + it->dur_ms = attr_ms(line, "DURATION", 0); + pl->has_parts = 1; + if (ends_with_ci(it->uri, ".m4s") || ends_with_ci(it->uri, ".mp4")) pl->is_fmp4 = 1; + } + /* Counted whether or not it was retained: a dropped part still + * occupies its slot in the source numbering, and the cursor in + * enqueue_new_media compares against it. */ + part_index++; } } else if (starts_with(line, "#EXTINF")) { /* #EXTINF:, — capture the duration for real-time pacing */ @@ -411,18 +485,22 @@ static void parse_media_playlist(const char* base, const char* text, hls_playlis while (*q) { const char* qnl = strchr(q, '\n'); int qlen = qnl ? (int)(qnl - q) : (int)strlen(q); - char uline[HLS_MAX_URI]; - if (qlen >= (int)sizeof(uline)) qlen = (int)sizeof(uline) - 1; - memcpy(uline, q, qlen); uline[qlen] = 0; - if (qlen && uline[qlen - 1] == '\r') uline[qlen - 1] = 0; - if (uline[0] && uline[0] != '#') { - if (pl->item_count < HLS_MAX_ITEMS) { - hls_item_t* it = &pl->items[pl->item_count++]; - resolve_url(base, uline, it->uri, sizeof(it->uri)); - it->msn = pl->media_seq_base + seg_index; - it->part = -1; - it->dur_ms = extinf_ms; - if (ends_with_ci(it->uri, ".m4s") || ends_with_ci(it->uri, ".mp4")) pl->is_fmp4 = 1; + if (qlen && q[0] != '#' && q[0] != '\r') { + char uline[HLS_MAX_URI], resolved[HLS_MAX_URI]; + /* A URI that is over-long or won't resolve leaves the segment + * unfetchable, but it is still a segment: seg_index has to + * count it either way or every later media sequence number + * shifts. */ + if (copy_line(uline, (int)sizeof(uline), q, qlen) && uline[0] && + resolve_url(base, uline, resolved, sizeof(resolved))) { + hls_item_t* it = playlist_append(pl, live); + if (it) { + snprintf(it->uri, sizeof(it->uri), "%s", resolved); + it->msn = pl->media_seq_base + seg_index; + it->part = -1; + it->dur_ms = extinf_ms; + if (ends_with_ci(it->uri, ".m4s") || ends_with_ci(it->uri, ".mp4")) pl->is_fmp4 = 1; + } } seg_index++; part_index = 0; /* parts now belong to the next in-progress segment */ @@ -474,7 +552,7 @@ static const char* queue_pop(basis_hls_t* h, long* out_dur_ms) { * skip the full segment, otherwise we take it. */ static void enqueue_new_media(basis_hls_t* h, const hls_playlist_t* pl) { for (int i = 0; i < pl->item_count; ++i) { - const hls_item_t* it = &pl->items[i]; + const hls_item_t* it = playlist_item(pl, i); if (it->part >= 0) { /* part P of segment M */ if (it->msn > h->want_msn || (it->msn == h->want_msn && it->part >= h->want_part)) { @@ -519,15 +597,18 @@ static int reload_and_enqueue(basis_hls_t* h) { int n = fetch_text(h, url, &text, &blocked); if (n < 0) { free(text); return blocked ? -2 : -1; } /* -2 = policy-blocked (deterministic) */ - hls_playlist_t pl; - parse_media_playlist(h->media_url, text, &pl); + /* Off the stack: the playlist is ~1 MiB and this runs on the producer thread. */ + hls_playlist_t* pl = (hls_playlist_t*)malloc(sizeof(*pl)); + if (!pl) { free(text); return -1; } + parse_media_playlist(h->media_url, text, pl); free(text); - if (pl.target_duration_ms) h->target_duration_ms = pl.target_duration_ms; - if (pl.has_endlist) h->endlist_seen = 1; + if (pl->target_duration_ms) h->target_duration_ms = pl->target_duration_ms; + if (pl->has_endlist) h->endlist_seen = 1; int before = h->pending_count; - enqueue_new_media(h, &pl); + enqueue_new_media(h, pl); + free(pl); return (h->pending_count > before) ? 1 : 0; } @@ -741,34 +822,40 @@ void* basis_hls_open(const char* url, const basis_http_provider_t* http, snprintf(h->media_url, sizeof(h->media_url), "%s", url); } - hls_playlist_t pl; - parse_media_playlist(h->media_url, text, &pl); + /* Off the stack: the playlist struct is ~1 MiB. */ + hls_playlist_t* pl = (hls_playlist_t*)malloc(sizeof(*pl)); + if (!pl) { free(text); free(h); return NULL; } + parse_media_playlist(h->media_url, text, pl); free(text); - if (pl.nfull == 0 && !pl.has_parts) { free(h); return NULL; } - - h->is_fmp4 = pl.is_fmp4; - h->can_block_reload = pl.can_block_reload && pl.has_parts; - h->part_target_ms = pl.part_target_ms; - h->target_duration_ms = pl.target_duration_ms ? pl.target_duration_ms : 6000; - h->endlist_seen = pl.has_endlist; - if (pl.has_endlist) { + /* Nothing fetchable. Either the playlist carries no media at all, or every + * URI in it was over the length cap — both are terminal, and failing here + * beats handing the producer a list it can only reload fruitlessly. */ + if (pl->item_count == 0) { free(pl); free(h); return NULL; } + + h->is_fmp4 = pl->is_fmp4; + h->can_block_reload = pl->can_block_reload && pl->has_parts; + h->part_target_ms = pl->part_target_ms; + h->target_duration_ms = pl->target_duration_ms ? pl->target_duration_ms : 6000; + h->endlist_seen = pl->has_endlist; + if (pl->has_endlist) { /* Sum whole segments only — parts subdivide the same media time. A VOD * beyond HLS_MAX_ITEMS is truncated at parse, so this under-reports in * lockstep with what actually plays. */ - for (int i = 0; i < pl.item_count; ++i) - if (pl.items[i].part < 0) h->total_ms += pl.items[i].dur_ms; + for (int i = 0; i < pl->item_count; ++i) + if (playlist_item(pl, i)->part < 0) h->total_ms += playlist_item(pl, i)->dur_ms; /* Retain the segment list so a seek can rebuild the queue from any * index. fMP4 VOD is excluded: a mid-stream ring flush would land the * demuxer inside a box, and it can't resynchronise the way TS does. */ - if (!pl.is_fmp4) { - h->vod_uri = (char (*)[HLS_MAX_URI])malloc((size_t)pl.item_count * HLS_MAX_URI); - h->vod_dur_ms = (long*)malloc((size_t)pl.item_count * sizeof(long)); + if (!pl->is_fmp4) { + h->vod_uri = (char (*)[HLS_MAX_URI])malloc((size_t)pl->item_count * HLS_MAX_URI); + h->vod_dur_ms = (long*)malloc((size_t)pl->item_count * sizeof(long)); if (h->vod_uri && h->vod_dur_ms) { - for (int i = 0; i < pl.item_count; ++i) { - if (pl.items[i].part >= 0) continue; - memcpy(h->vod_uri[h->vod_count], pl.items[i].uri, HLS_MAX_URI); - h->vod_dur_ms[h->vod_count] = pl.items[i].dur_ms; + for (int i = 0; i < pl->item_count; ++i) { + const hls_item_t* it = playlist_item(pl, i); + if (it->part >= 0) continue; + memcpy(h->vod_uri[h->vod_count], it->uri, HLS_MAX_URI); + h->vod_dur_ms[h->vod_count] = it->dur_ms; h->vod_count++; } } else { @@ -777,7 +864,7 @@ void* basis_hls_open(const char* url, const basis_http_provider_t* http, } } } - if (pl.map_uri[0]) snprintf(h->map_uri, sizeof(h->map_uri), "%s", pl.map_uri); + if (pl->map_uri[0]) snprintf(h->map_uri, sizeof(h->map_uri), "%s", pl->map_uri); /* VOD (EXT-X-ENDLIST): start at the first segment so the whole recording * plays start-to-finish. Live: start at (or just behind) the live edge. @@ -785,23 +872,27 @@ void* basis_hls_open(const char* url, const basis_http_provider_t* http, * of a segment is normally an independent keyframe). Non-LL live: the last * complete segment (guaranteed keyframe). */ if (h->endlist_seen) { - h->want_msn = pl.media_seq_base; + h->want_msn = pl->media_seq_base; h->want_part = 0; } else if (h->can_block_reload) { - h->want_msn = pl.media_seq_base + pl.nfull; + h->want_msn = pl->media_seq_base + pl->nfull; h->want_part = 0; } else { /* Start a few segments behind the live edge so playout always has a buffer and * segment fetches never wait on the encoder (plain HLS has no parts to ride). Each - * segment starts on a keyframe, so any of these is a valid decode start. */ - long edge = pl.media_seq_base + (pl.nfull > 0 ? pl.nfull - 1 : 0); + * segment starts on a keyframe, so any of these is a valid decode start. Clamp to + * the oldest retained item, not to media_seq_base: a DVR window lists more + * segments than are kept, and the ones before item[0] were rolled out. */ + long edge = pl->media_seq_base + (pl->nfull > 0 ? pl->nfull - 1 : 0); long start = edge - HLS_LIVE_MARGIN_SEGMENTS; - if (start < pl.media_seq_base) start = pl.media_seq_base; + long oldest = playlist_item(pl, 0)->msn; + if (start < oldest) start = oldest; h->want_msn = start; h->want_part = 0; } - enqueue_new_media(h, &pl); + enqueue_new_media(h, pl); + free(pl); /* Start the read-ahead producer so segments buffer ahead of playout. */ h->ring_cap = HLS_RING_CAP; diff --git a/Basis/Packages/com.basis.mediaplayer/Native~/protocol/basis_url.c b/Basis/Packages/com.basis.mediaplayer/Native~/protocol/basis_url.c index 7a90b8d21..2cbc7c5a3 100644 --- a/Basis/Packages/com.basis.mediaplayer/Native~/protocol/basis_url.c +++ b/Basis/Packages/com.basis.mediaplayer/Native~/protocol/basis_url.c @@ -71,7 +71,10 @@ int basis_url_parse(const char* url, basis_url_t* out) { * host:port. */ if (*host_end) { size_t pathlen = strlen(host_end); - if (pathlen >= sizeof(out->path)) pathlen = sizeof(out->path) - 1; + /* Reject rather than truncate. Callers dispatch on what the path ends + * with, so a clipped path silently reroutes the URL to the wrong + * handler instead of failing where it can be reported. */ + if (pathlen >= sizeof(out->path)) return -1; memcpy(out->path, host_end, pathlen); out->path[pathlen] = 0; } else { diff --git a/Basis/Packages/com.basis.mediaplayer/Native~/protocol/basis_url.h b/Basis/Packages/com.basis.mediaplayer/Native~/protocol/basis_url.h index c2b161908..529847313 100644 --- a/Basis/Packages/com.basis.mediaplayer/Native~/protocol/basis_url.h +++ b/Basis/Packages/com.basis.mediaplayer/Native~/protocol/basis_url.h @@ -10,7 +10,11 @@ typedef struct basis_url { char scheme[16]; /* lowercased: rtsp, rtspt, rtmp, rtmps, http, https, rist */ char host[256]; int port; /* defaulted per scheme when absent */ - char path[1024]; /* everything after the host, including leading '/' and query */ + /* Everything after the host, including leading '/' and query. Sized to hold + * the path of any URL the engine accepts (its url buffer is 2048), because + * callers route on the path's content — a CDN-signed URL that overflowed + * here would lose its ".m3u8" tail and be dispatched as something it isn't. */ + char path[2048]; char user[128]; char pass[128]; int tls; /* 1 for rtmps/https */ diff --git a/Basis/Packages/com.basis.mediaplayer/Plugins/Android/arm64-v8a/libbasis_media_native.so b/Basis/Packages/com.basis.mediaplayer/Plugins/Android/arm64-v8a/libbasis_media_native.so index fb31374f0..bb4ffa12b 100644 Binary files a/Basis/Packages/com.basis.mediaplayer/Plugins/Android/arm64-v8a/libbasis_media_native.so and b/Basis/Packages/com.basis.mediaplayer/Plugins/Android/arm64-v8a/libbasis_media_native.so differ diff --git a/Basis/Packages/com.basis.mediaplayer/Plugins/Windows/x86_64/basis_media_native.dll b/Basis/Packages/com.basis.mediaplayer/Plugins/Windows/x86_64/basis_media_native.dll index d230a2c4c..29168e995 100644 Binary files a/Basis/Packages/com.basis.mediaplayer/Plugins/Windows/x86_64/basis_media_native.dll and b/Basis/Packages/com.basis.mediaplayer/Plugins/Windows/x86_64/basis_media_native.dll differ