Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix: proxify streams to use plugins session parameters
- Loading branch information
Showing
16 changed files
with
1,097 additions
and
197 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
import re | ||
from dataclasses import dataclass | ||
from typing import Dict, Iterable, Optional, Tuple | ||
|
||
|
||
class HashableDict(dict): # noqa: WPS600 | ||
def __hash__(self): | ||
return hash(frozenset(self.items())) | ||
|
||
|
||
@dataclass(frozen=True) | ||
class StreamSessionOpts(object): | ||
service: str | ||
session_headers: Optional[HashableDict] | ||
|
||
|
||
@dataclass(frozen=True) | ||
class Stream(object): | ||
url: str | ||
protocol: str | ||
session: Optional[StreamSessionOpts] = None | ||
audio_tracks: Optional["Streams"] = None | ||
|
||
|
||
class Streams(object): | ||
def __init__(self, streams: Optional[Dict[str, Stream]] = None): | ||
if streams: | ||
self.streams = HashableDict(streams) | ||
else: | ||
self.streams = HashableDict() | ||
|
||
def __hash__(self): | ||
return hash(self.streams) | ||
|
||
def __getitem__(self, key): | ||
return self.streams[key] | ||
|
||
def __setitem__(self, key, value): # noqa: WPS110 | ||
self.streams[key] = value | ||
|
||
def __len__(self): | ||
return len(self.streams) | ||
|
||
def __iter__(self): | ||
return iter(self.streams) | ||
|
||
def __reversed__(self): | ||
return reversed(self.streams) | ||
|
||
def items(self) -> Iterable[Tuple[str, Stream]]: # noqa: WPS110 | ||
return self.streams.items() | ||
|
||
@property | ||
def best(self) -> Tuple[str, Stream]: | ||
return list(self.streams.items())[-1] | ||
|
||
@property | ||
def worst(self) -> Tuple[str, Stream]: | ||
return list(self.streams.items())[0] | ||
|
||
def by_quality(self, quality: str) -> Tuple[str, Stream]: | ||
if quality == "best": | ||
return self.best | ||
|
||
if quality == "worst": | ||
return self.worst | ||
|
||
if self.streams.get(quality): | ||
return quality, self.streams[quality] | ||
|
||
return self._guess_quality(quality) | ||
|
||
def _guess_quality(self, quality: str) -> Tuple[str, Stream]: | ||
quality_lines = re.search(r"^(\d+)", quality) | ||
if not quality_lines: | ||
return self.best | ||
|
||
quality_lines = int(quality_lines.group(1)) | ||
for quality_code, stream_url in reversed(self.streams.items()): | ||
stream_lines = re.search(r"^(\d+)", quality_code) | ||
if not stream_lines: | ||
continue | ||
|
||
stream_lines = int(stream_lines.group(1)) | ||
|
||
if stream_lines <= quality_lines: | ||
return quality_code, stream_url | ||
|
||
return self.best |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
from threading import Lock | ||
|
||
from gridplayer.models.stream import Stream | ||
from gridplayer.player.managers.base import ManagerBase | ||
from gridplayer.utils.stream_proxy.stream_proxy import StreamProxy | ||
|
||
|
||
class StreamProxyManager(ManagerBase): | ||
def __init__(self, **kwargs): | ||
super().__init__(**kwargs) | ||
|
||
self._stream_proxy_lock = Lock() | ||
self._stream_proxy = None | ||
|
||
@property | ||
def stream_proxy(self): | ||
with self._stream_proxy_lock: | ||
if self._stream_proxy is None: | ||
self._stream_proxy = StreamProxy(parent=self) | ||
self._stream_proxy.start() | ||
|
||
return self._stream_proxy | ||
|
||
@property | ||
def commands(self): | ||
return { | ||
"add_stream": self.cmd_add_stream, | ||
} | ||
|
||
def cmd_add_stream(self, stream: Stream) -> str: | ||
return self.stream_proxy.add_stream(stream) | ||
|
||
def cleanup(self): | ||
if self._stream_proxy is not None: | ||
self._stream_proxy.cleanup() | ||
self._stream_proxy = None |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
from typing import List | ||
|
||
from streamlink.stream.hls_playlist import M3U8, ByteRange, Segment | ||
|
||
LIVESTREAM_EDGE = 16 | ||
|
||
|
||
def m3u8_to_str(hls_playlist: M3U8): | ||
res = ["#EXTM3U"] | ||
res += [f"#EXT-X-VERSION:{hls_playlist.version}"] | ||
res += [f"#EXT-X-TARGETDURATION:{hls_playlist.target_duration}"] | ||
if hls_playlist.playlist_type: | ||
res += [f"#EXT-X-PLAYLIST-TYPE:{hls_playlist.playlist_type}"] | ||
res += [f"#EXT-X-MEDIA-SEQUENCE:{hls_playlist.media_sequence}"] | ||
|
||
# grab only the edge if it's a livestream | ||
if hls_playlist.media_sequence: | ||
segments = hls_playlist.segments[-LIVESTREAM_EDGE:] | ||
else: | ||
segments = hls_playlist.segments | ||
|
||
res += sum([_segment_to_str(s) for s in segments], []) | ||
|
||
if hls_playlist.is_endlist: | ||
res += ["#EXT-X-ENDLIST"] | ||
|
||
return "\n".join(res) | ||
|
||
|
||
def _segment_to_str(segment: Segment) -> List[str]: | ||
res = [] | ||
|
||
if segment.date: | ||
timestamp = segment.date.isoformat(timespec="seconds") | ||
timestamp = timestamp.replace("+00:00", "Z") | ||
res += [f"#EXT-X-PROGRAM-DATE-TIME:{timestamp}"] | ||
if segment.discontinuity: | ||
res += ["#EXT-X-DISCONTINUITY"] | ||
if segment.byterange: | ||
res += ["#EXT-X-BYTERANGE:{0}".format(_byterange_to_str(segment.byterange))] | ||
if segment.map: | ||
res += [ | ||
'#EXT-X-MAP:URI="{0}"{1}'.format( | ||
segment.map.uri, | ||
',BYTERANGE="{0}"'.format(_byterange_to_str(segment.map.byterange)) | ||
if segment.map.byterange | ||
else "", | ||
) | ||
] | ||
|
||
res += [ | ||
"#EXTINF:{0},{1}".format( | ||
segment.duration, segment.title if segment.title else "" | ||
) | ||
] | ||
|
||
res += [segment.uri] | ||
|
||
return res | ||
|
||
|
||
def _byterange_to_str(byterange: ByteRange) -> str: | ||
offset_txt = f"@{byterange.offset}" if byterange.offset else "" | ||
return f"{byterange.range}{offset_txt}" |
Oops, something went wrong.