More types of messages are now supported than before.
SubscribeError handling has been improved.
A new error has been added when no catalog.
Existing messages have been reviewed to eliminate inconsistencies in the
migration from draft-11 to draft-14 support.
Fix log levels
Allow evict the buffer more aggressive in MSF streams
Do not create a prefetch because for MSF it is not necessary because the
data is always in the segments.
Set MSF streams as low latency.
## Summary
- In MSF live streams, audio and video segments arrive at different
wall-clock times over WebTransport (audio frames are ~8KB vs ~125KB
video keyframes, so audio arrives first).
- The first stream type to deliver a segment calls `lockStartTime()` and
`setSegmentAvailabilityDuration(0.5)`, creating a tight availability
window. When the other type's segments arrive even slightly later,
`mergeAndEvict` immediately evicts them because they fall before
`getSegmentAvailabilityStart()`.
- This causes video to never render — StreamingEngine repeatedly logs
`(video:N) cannot find segment` because every video segment is evicted
on arrival.
### Fix
1. Added `firstSegmentStartTimes_` map to track the first segment start
time per content type.
2. Before the timeline is locked, pass `0` as the eviction start to
`mergeAndEvict` to prevent premature segment eviction.
3. Only call `lockStartTime()` once ALL expected stream types (audio AND
video) have received at least one segment.
4. Set `segmentAvailabilityDuration` wide enough to cover the gap
between the earliest and latest stream start times (`gap + duration`),
so the stream whose segments arrived first isn't continuously evicted.
Co-authored-by: Erik Herz <erik@vivoh.com>
## Summary
- When MSF catalog track objects don't include an explicit `namespace`
field, `subscribeToTrack` falls through to an empty string. This causes
SUBSCRIBE messages with an empty namespace that the relay cannot match
to any published broadcast.
- The MSF catalog spec does not require per-track namespace fields — the
namespace is established at the transport level via PUBLISH_NAMESPACE.
Shaka should fall back to the session namespace.
- Fix: fall back to `config.msf.namespaces`, then to
`publishNamespaces_` (from PUBLISH_NAMESPACE), before defaulting to
empty string.
---------
Co-authored-by: Erik Herz <erik@vivoh.com>
Co-authored-by: Álvaro Velad Galván <ladvan91@hotmail.com>
## Problem
The SubgroupHeader stream type detection only covers the draft-11 range
(`0x08-0x0D`), missing the two new ranges added in draft-14:
- `0x10-0x15`: without EndOfGroup
- `0x18-0x1D`: with EndOfGroup
https://www.ietf.org/archive/id/draft-ietf-moq-transport-14.html#section-10.4.2-5
When a relay or publisher sends a stream with a type in the new ranges
(e.g., `0x10`), it is treated as an unknown stream type and not
processed. Media data on that stream is lost.
## Changes
- Expand stream type checks, extension header detection, and SubgroupID
resolution logic to cover the new ranges
- Remove unused constants (`SUBGROUP_HEADER_START_BIGINT`,
`SUBGROUP_HEADER_END_BIGINT`)
## Testing
Tested manually with a draft-14 publisher
([moqtail](https://github.com/moqtail/moqtail)) and relay
([moq-wasm](https://github.com/nttcom/moq-wasm)).
Existing behavior for `0x08-0x0D` is preserved (pure addition).
---------
Co-authored-by: Claude Code <noreply@anthropic.com>
Co-authored-by: Álvaro Velad Galván <ladvan91@hotmail.com>
## Summary
MSF works in uncompiled mode but fails with error 4058
(`MSF_VOD_CONTENT_NOT_SUPPORTED`) in the compiled build.
## Cause
The `MSFTrack` and `MSFCatalog` typedefs are defined in regular source
code (`msf_utils.js`). Closure Compiler mangles their property names in
the compiled build (e.g., `track.isLive` → `track.sa`).
Since the catalog is received as external JSON via `JSON.parse()`, the
actual keys remain unchanged and all property accesses return
`undefined`.
Moving the typedefs to an externs file tells Closure Compiler to
preserve the property names.
## Changes
- Move `MSFTrack` and `MSFCatalog` typedefs to a new externs file
(`externs/msf_catalog.js`) with the `@externs` annotation
- Update type references in `msf_parser.js` to use the new extern types
- Remove the original typedefs from `msf_utils.js`
## Testing
Tested manually with a draft-14 publisher
([moqtail](https://github.com/moqtail/moqtail)) and relay
([moq-wasm](https://github.com/nttcom/moq-wasm)).
---------
Co-authored-by: Claude Code <noreply@anthropic.com>
## Summary
`subscribeTrack()` in `msf_tracks_manager.js` wraps the namespace string
as a 1-element tuple (e.g., `["moq-chat/chat"]`) instead of splitting it
back into the original tuple (e.g., `["moq-chat", "chat"]`).
These produce different bytes on the wire, so the relay cannot match the
SUBSCRIBE against the publisher's namespace. This affects all SUBSCRIBE
messages including catalog, video, and audio tracks.
## Details
Internally, Shaka Player represents the namespace as a `/`-joined string
(see `subscribeToCatalog_()` in `msf_parser.js:341`).
When encoding a SUBSCRIBE message, the string must be split by `/` to
reconstruct the original tuple. But the code was using `[namespace]`
instead of `namespace.split('/')`.
## Changes
Replace `namespace: [namespace]` with `namespace: namespace.split('/')`
in `subscribeTrack()`.
## Testing
Tested manually with a draft-14 publisher
([moqtail](https://github.com/moqtail/moqtail)) and relay
([moq-wasm](https://github.com/nttcom/moq-wasm)).
---------
Co-authored-by: Claude Code <noreply@anthropic.com>
So basically, when a license request fails (eg. network Error, server
down whatever), apps can now retry from scratch by calling
`player.retryLicensing()`. This was tricky to implement because of EME
spec limitations: `generateRequest()` can only be called once per
session. So if it fails, it would be stuck.
So I close the old session and create a brand new one with the same
`initData`
> Will Video element throw an error during this process?
we were worried that closing the session would leave the video without
keys for a brief moment, potentially triggering errors. But in practice,
the transition is fast enough( I added a 0.1s delay for CDM clean up)
and the video element handles it gracefully
> Will new encrypted event fire? If not, will it limit this feature?
The encrypted event only fires when the browser first encounters
encrypted content. When we close and recreate a session, the content is
already loaded, so no new event
Solutions: In `CreateSession()` metadata store `initData` and
`initDataType` in the session metadata when the session is first
created. So when `retryLicensing()`is called, we just grab the stored
data and pass it to `generateRequest()` on the new session. No need to
wait for an `encrypted` event at all.
---------
Co-authored-by: Álvaro Velad Galván <ladvan91@hotmail.com>
Co-authored-by: Wojciech Tyczyński <tykus160@gmail.com>