- Make `SegmentBase`, `SegmentList` and `SegmentTemplate` inherit from
the common `_SegmentBaseType` and `_MultipleSegmentBaseType` classes
- Logically move class definitions
- Define common attributes in `_{,Multiple}SegmentBaseType`
that get inherited from nodes of the same type in ancestor nodes,
and find common child nodes
- Find inherited attributes independently
- Fix, clean up, reorder and selectively add attributes
- Add `_SegmentBaseType.availabilityTimeOffset` (currently unused)
- Fix `MPD.minBufferTime` type (required attribute which can't be None)
- Make `AdaptationSet` and `Representation` inherit from the common
`_RepresentationBaseType` class
- Move and logically group class definitions
- Define common attributes in `_RepresentationBaseType` that get
inherited from ancestor nodes of the same base type, and find
common child nodes
- Fix, clean up, reorder and selectively add attributes
- Remove `Representation.numChannels` (invalid and unused)
- Turn `Representation.lang` into a property which reads its value
from the parent `AdaptationSet.lang`
(it's not an attribute on `Representation` according to the spec,
but it gets read by `DASHStream.parse_manifest`, so keep the alias)
- Rename `contentProtection` attribute to `contentProtections`
and `subRepresentation` to `subRepresentations`, to stay consistent
with other attributes of child node lists
- Fix tests with old assertions: non-inhertied attributes that led to
different expectations (e.g. the resulting stream name)
Refactor `walk_back()`:
- compare ancestor nodes by type and not by tag name
- rename mapper parameter and check its result before yielding the node
Refactor `walk_back_get_attr()`:
- allow finding attributes from specific ancestor nodes only
- stop iterating after the first match
Refactor `attr(inherited=...)`:
- allow inheriting from ancestor nodes, optionally from a specific type,
and not just the parent node
Inherit attributes from ancestor `SegmentList` nodes, if set,
similar to `SegmentTemplate`.
Also fix `presentationTimeOffset` not being inherited in
`SegmentTemplate`.
TODO: use common `MultipleSegmentBaseType` class for these attributes
- Turn `SegmentList` into an only-child in `Period`, `AdaptationSet`
and `Representation`
- Set `parent` type in `SegmentList`
- Turn `SegmentList.segments` from a property into a regular method,
similar to `SegmentTemplate.segments()`
- Move segment timeline logic from `SegmentTemplate.format_media()`
into the new method `SegmentTemplate.segment_timeline()`, similar to
`SegmentTemplate.segment_numbers()`
- Simplify segment timeline generator
- Use a threshold `datetime` for the presentation delay
- Don't collect every segment on subsequent manifests
- Include segment number for timeline segments (for the debug log)
- Rewrite continued dynamic timeline test (same results)
Instead of passing `os.devnull` file-handles to `subprocess.Popen`,
use the `subprocess.DEVNULL` identifier for stdout/stderr streams.
This avoids `ResourceWarning`s in tests when the file-handles don't get
closed despite never calling `PlayerOutput.open()`, as they were already
opened in the `PlayerOutput`'s constructor, e.g. during test collection
in parametrized tests without indirect fixtures.
Also call `PlayerOutput.close()` in tests where a *mocked* player output
was explicitly opened.
Follow-up of f234c690
Properly fix the `SegmentedStreamWriter`'s `_wait` Event when closing
the thread.
Depending on which thread attempted to close the writer thread,
the `close()` method's `self.reader.close()` call can block, leading
to the `_wait` event to not get set until all threads have terminated
and joined. So make sure to set the event first, so that all threads
of the writer's thread-pool executor can be stopped first,
for example segment downloads waiting for the segment's availability.
- Add `number` attribute to `Segment` and move `available_at` further up
- Pass all attributes on each `Segment` initialization
- Return "initialization", the segment number or the URL's path name
as the segment's `name`
- Log the segment `availability` time in addition to the current time
- Use better debug log messages while fetching segments
- Import `datetime` and `timedelta` from `datetime` in order to
improve readability and avoid wrapping of long lines
- Use `streamlink.utils.times` utilities and constants
The `SegmentedStreamWriter`'s `_wait` event needs to be set before
awaiting the shutdown of the thread-pool executor, otherwise the threads
of the executor won't ever receive this signal. For example, queued
segment downloads can't be cancelled because the event never gets set
before awaiting the shutdown of the executor, so `wait()` calls in the
executor's threads time out regularly and return `True`.
- Add `period` parameter to `DASHStream.parse_manifest()`:
This can be used by the DASH plugin, where parameters can be passed
from the input URL
- Remove `period` parameter from the `DASHStream` constructor
and reference the period from the chosen `Representation`
- Move `get_representation()` from `DASHStreamWorker` to `MPD`
- Update tests
CDN auth data is only required at certain times of the day,
and if missing, DASH segment requests result in a 403 response.
Also remove retry-loop with `time.sleep()` calls when trying to access
a stream while it's in the waiting state. Just return `None` and let
the user try again on their own.
- Add typing annotations to all `MPDNode` children
- Fix various typing issues
- Override optional types of required attributes
- Add checks for optional stuff in segment processing
- Fix processing of empty node texts
- Fix typing in `DASHStream` caused by enabling typing in `MPDNode`s
- Fix order of `*args` and keywords
- Remove `*args` from `MPDNode`
- Capture `node`, `root` and `parent` in `*args` and `**kwargs`
- TODO/PEP570: set positional-only and keyword-only parameters (py38)
Add overloaded typing information to `MPDNode.attr()` to be able to
have more precise typing of return values. Return values of required
attributes are still typed as optional and need to be overridden.
Also change the order of parameters.
- Fix segment number iterator and segment availability-time iterator
for dynamic timeline-less DASH streams by calculating the right
segment number offset and basing the segment availability-time on
the segment number, and not just on the current time alone
- Set a default value of 0 seconds for presentationTimeOffset
- Ensure that segment durations are known
- Never calculate negative segment number offsets
- Reduce unnecessary delay by starting with the upcoming segment in the
generated timeline to be as close to the live-edge as possible
(with minBufferTime and presentation offset+delay in mind)
- Add debug log output
- Update MPD manifest test fixture with better time data
Next:
- Consider removing the default value of suggestedPresentationDelay,
as it seems to be a relict of Streamlink's initial DASH implementation
- Also take a look at properly synchronizing the generated timeline
between multiple substreams, so that the correct segment numbers
always get calculated in all threads
- Avoid interweaving debug log and make it flush per-thread
- Convert `isodate.Duration` instances to `datetime.timedelta`
in `SegmentTemplate.format_media()` in order to be able to compare
with `datetime.datetime` in the timeline generator
- Keep `isodate.Duration` or `datetime.timedelta` instances
in `SegmentTemplate.segment_numbers()` before subtracting from
`since_start`, as `isodate.Duration()` can't be converted to seconds
without a reference `datetime.datetime`
- Replace all `isodate.Duration()` attribute default values with
`datetime.timedelta()` for simplicity/performance reasons
- Set the 3 seconds default value of `MPD.suggestedPresentationDelay`
on the attribute's default definition instead of using constants
in the `SegmentTemplate` methods
The segment availability "anchor time" depends on the sum of the
manifest's `availabilityStartTime` and the period's `start` attribute,
for both static and dynamic manifests.
Fix segment availability times:
- Set the correct `available_at` value for static manifests with segment
templates and segment timelines instead of using the current time
- Set the `available_at` value for `SegmentList` segments instead of
defaulting to `EPOCH_START`
- Set the `available_at` value for all initialization segments
Fix segment numbers:
- The number offset now also takes the period start into consideration
Also:
- Allow passing keywords to child node constructors
- Keep the `Period` reference on `SegmentList`, `SegmentTemplate`
and `Representation`
- Check segment availability times in certain tests
- Rename DASH manifest fixture files of updated tests
- Add "DTZ" ruff rule
- Add utility functions to `streamlink.utils.times` which use
"aware" datetimes with explicit timezone information,
and use `isodate`'s local timezone implementation
- Replace all "naive" datetimes without timezone information
- Replace all custom ISO8601 parsers with `isodate`'s implementation
- Add tests for new utility functions
Use the newly added `Representation.ident` for finding the same
`Representation` after a manifest reload of a dynamic DASH stream.
Also use the worker's `period` attribute instead of the hardcoded
zero index, even though only the first period is supported.
When building segment timelines, don't use the `mimeType` attribute
of the `SegmentTemplate`'s parent node as a fallback when the parent's
`id` attribute is missing.
This doesn't work if the parent node is a `Period`, as it doesn't have
the `mimeType` attribute, and it also generates timeline keys which
are not unique if two streams of the same `mimeType` would get
muxed (currently not supported).
Instead, build an `ident` tuple on the `Representation` instance
consisting of the ids of the parent `Period` and `AdaptationSet`, and
the `Representation` itself, with its required `id` attribute. This
ensures a unique key for the `timelines` dict.
Then simply pass the `ident` tuple to the `SegmentTemplate.segments()`
generator, similar to the `BaseURL` fixes of e684913.
- Fix incorrect typing
- Fix error messages, as well as docstrings and comments
- Fix indentation
- Replace str.format with f-strings
- Use consistent order of `MPDNode.attr()` call keywords,
but keep the order of the method's arguments
- Use keywords when initializing the `Segment` dataclass
- Flatten nested if-blocks in `SegmentTemplate.format_media()`
- Remove unnecessary if-block in `Representation.segments()`