Skip to content

stream.hls: implement media initialization section #3828

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

Merged

Conversation

bastimeyer
Copy link
Member

@bastimeyer bastimeyer commented Jun 26, 2021

Resolves #3328
Resolves #3403

This implements the "Media Initialization Section" of HLS segments (via the EXT-X-MAP HLS tag) which is required for streams which use the fragmented MPEG-4 format (or MPEG2-TS with optional initialization sections). Think of it as segment headers which are loaded from an external source.

3.2 MPEG-2 Transport Streams
The Media Initialization Section of an MPEG-2 Transport Stream Segment is a Program Association Table (PAT) followed by a Program Map Table (PMT).

Transport Stream Segments MUST contain a single MPEG-2 Program; playback of Multi-Program Transport Streams is not defined. Each Transport Stream Segment MUST contain a PAT and a PMT, or have an EXT-X-MAP tag (Section 4.3.2.5) applied to it. The first two Transport Stream packets in a Segment without an EXT-X-MAP tag SHOULD be a PAT and a PMT.

3.3 Fragmented MPEG-4
MPEG-4 Fragments are specified by the ISO Base Media File Format [ISOBMFF]. Unlike regular MPEG-4 files that have a Movie Box ('moov') that contains sample tables and a Media Data Box ('mdat') containing the corresponding samples, an MPEG-4 Fragment consists of a Movie Fragment Box ('moof') containing a subset of the sample table and a Media Data Box containing those samples. Use of MPEG-4 Fragments does require a Movie Box for initialization, but that Movie Box contains only non-sample-specific information such as track and sample descriptions.
...
Each fMP4 Segment in a Media Playlist MUST have an EXT-X-MAP tag applied to it.

This simply means that if a map exists, we always have to write the map contents (the segment initialization section) to the output stream before writing the actual segment contents, regardless the used format.


Split into two commits:

  1. type hints and clean up with some minor fixes of the M3U8 parser
    All namedtuples are now typed, which makes referencing data much easier. What still needs to be done in the future is implementing all missing and new tag attributes from newer HLS versions according to RFC8216.
  2. refactor of SegmentedStreamWriter and HLSStreamWriter, and feature implementation
    The map content requests all get cached with a max cache number equal to the number of writer threads, so that it doesn't have to re-request the initialization section(s) every time when downloading a new segment.

The changes should also include a fix for the wonky test coverage results, which were unstable in the SegmentedStreamWriter due to the threading and queue polling. It simply disables coverage on those parts.


Other subclasses of SegmentedStreamWriter should not be affected by these changes unless they override queue() or put(), but that is not the case.

- replace collections.namedtuple with typing.NamedTuple
- add type hints to most functions
- make methods static where possible
- call super()
- reformat code

M3U8Parser:
- replace tuple with ExtInf data with `ExtInf` NamedTuple
- annotate HLS version compatibilities
@bastimeyer bastimeyer force-pushed the stream/hls/initialization-section branch 2 times, most recently from 39adaf7 to 70df732 Compare June 27, 2021 05:04
Implement segment media initialization section via `#EXT-X-MAP`.
Supports encrypted initializations via `#EXT-X-KEY`.

- implement cached requests (use thread count as max cache size)
- refactor SegmentedStreamWriter:
  - remove queue parameter from queue()
  - re-order tuple items which are queued and add optional data
- override SegmentedStreamWriter.put in HLSStreamWriter:
  - always queue segment initialization if map data exists
  - cache map requests and queue cached requests if they exist,
    so that it only downloads initialization sections once
- split HLSStreamWriter.fetch into two: for Segments and for Maps:
  both have the `uri` and `byterange` attributes
- call super()
- add tests, improve minor stuff
@bastimeyer bastimeyer force-pushed the stream/hls/initialization-section branch from 70df732 to 6a2acf6 Compare June 27, 2021 11:17
Copy link
Member

@gravyboat gravyboat left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good, thanks @bastimeyer.

@gravyboat gravyboat merged commit 66027b5 into streamlink:master Jun 27, 2021
@bastimeyer bastimeyer deleted the stream/hls/initialization-section branch June 27, 2021 22:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Modern fmp4 HLS Incompatibility
3 participants