1
0
mirror of https://github.com/fHDHR/fHDHR_NextPVR.git synced 2025-12-06 10:16:58 -05:00

Improve Direct Streaming

This commit is contained in:
deathbybandaid 2020-11-13 14:08:55 -05:00
parent 9847f72e6c
commit f9883ab064
3 changed files with 59 additions and 32 deletions

View File

@ -78,7 +78,7 @@ class Tuners():
channelUri_headers = self.fhdhr.web.session.head(stream_args["channelUri"]).headers channelUri_headers = self.fhdhr.web.session.head(stream_args["channelUri"]).headers
stream_args["true_content_type"] = channelUri_headers['Content-Type'] stream_args["true_content_type"] = channelUri_headers['Content-Type']
if stream_args["true_content_type"].startswith("application/"): if stream_args["true_content_type"].startswith(tuple(["application/", "text/"])):
stream_args["content_type"] = "video/mpeg" stream_args["content_type"] = "video/mpeg"
else: else:
stream_args["content_type"] = stream_args["true_content_type"] stream_args["content_type"] = stream_args["true_content_type"]

View File

@ -1,7 +1,7 @@
import time import time
import re import m3u8
import urllib.parse
from Crypto.Cipher import AES
# from fHDHR.exceptions import TunerError # from fHDHR.exceptions import TunerError
@ -20,14 +20,18 @@ class Direct_Stream():
if not self.stream_args["duration"] == 0: if not self.stream_args["duration"] == 0:
self.stream_args["time_end"] = self.stream_args["duration"] + time.time() self.stream_args["time_end"] = self.stream_args["duration"] + time.time()
if not re.match('^(.*m3u8)[\n\r]*$', self.stream_args["channelUri"]): if not self.stream_args["true_content_type"].startswith(tuple(["application/", "text/"])):
self.fhdhr.logger.info("Direct Stream of URL: %s" % self.stream_args["channelUri"]) self.fhdhr.logger.info("Direct Stream of %s URL: %s" % (self.stream_args["true_content_type"], self.stream_args["channelUri"]))
req = self.fhdhr.web.session.get(self.stream_args["channelUri"], stream=True) req = self.fhdhr.web.session.get(self.stream_args["channelUri"], stream=True)
def generate(): def generate():
try: try:
chunk_counter = 1
while self.tuner.tuner_lock.locked(): while self.tuner.tuner_lock.locked():
for chunk in req.iter_content(chunk_size=self.chunksize): for chunk in req.iter_content(chunk_size=self.chunksize):
@ -41,7 +45,10 @@ class Direct_Stream():
if not chunk: if not chunk:
break break
# raise TunerError("807 - No Video Data") # raise TunerError("807 - No Video Data")
self.fhdhr.logger.info("Passing Through Chunk #%s with size %s" % (chunk_counter, self.chunksize))
yield chunk yield chunk
self.fhdhr.logger.info("Connection Closed: Tuner Lock Removed") self.fhdhr.logger.info("Connection Closed: Tuner Lock Removed")
except GeneratorExit: except GeneratorExit:
@ -57,15 +64,14 @@ class Direct_Stream():
self.fhdhr.logger.info("Detected stream URL is m3u8: %s" % self.stream_args["true_content_type"]) self.fhdhr.logger.info("Detected stream URL is m3u8: %s" % self.stream_args["true_content_type"])
# Determine if this m3u8 contains variants or chunks
channelUri = self.stream_args["channelUri"] channelUri = self.stream_args["channelUri"]
self.fhdhr.logger.info("Opening m3u8 URL: %s" % channelUri) while True:
m3u8_get = self.fhdhr.web.session.get(self.stream_args["channelUri"])
m3u8_content = m3u8_get.text videoUrlM3u = m3u8.load(channelUri)
variants = [urllib.parse.urljoin(self.stream_args["channelUri"], line) for line in m3u8_content.split('\n') if re.match('^(.*m3u8)[\n\r]*$', line)] if len(videoUrlM3u.playlists):
if len(variants): channelUri = videoUrlM3u.playlists[0].absolute_uri
channelUri = variants[0] else:
self.fhdhr.logger.info("m3u8 contained variants. Using URL: %s" % channelUri) break
def generate(): def generate():
@ -75,20 +81,29 @@ class Direct_Stream():
while self.tuner.tuner_lock.locked(): while self.tuner.tuner_lock.locked():
m3u8_get = self.fhdhr.web.session.get(channelUri) playlist = m3u8.load(channelUri)
m3u8_content = m3u8_get.text segments = playlist.segments
chunk_urls_detect = [urllib.parse.urljoin(channelUri, line) for line in m3u8_content.split('\n') if re.match('^(.*ts)[\n\r]*$', line)]
if len(played_chunk_urls):
newsegments = 0
for segment in segments:
if segment.absolute_uri not in played_chunk_urls:
newsegments += 1
self.fhdhr.logger.info("Refreshing m3u8, Loaded %s new segments." % str(newsegments))
else:
self.fhdhr.logger.info("Loaded %s segments." % str(len(segments)))
if playlist.keys != [None]:
keys = [{"url": key.uri, "method": key.method, "iv": key.iv} for key in playlist.keys if key]
else:
keys = [None for i in range(0, len(segments))]
for segment, key in zip(segments, keys):
chunkurl = segment.absolute_uri
chunk_urls_play = []
for chunkurl in chunk_urls_detect:
if chunkurl not in played_chunk_urls: if chunkurl not in played_chunk_urls:
chunk_urls_play.append(chunkurl)
played_chunk_urls.append(chunkurl) played_chunk_urls.append(chunkurl)
for chunkurl in chunk_urls_play:
self.fhdhr.logger.info("Passing Through Chunk: %s" % chunkurl)
if (not self.stream_args["duration"] == 0 and if (not self.stream_args["duration"] == 0 and
not time.time() < self.stream_args["time_end"]): not time.time() < self.stream_args["time_end"]):
self.fhdhr.logger.info("Requested Duration Expired.") self.fhdhr.logger.info("Requested Duration Expired.")
@ -98,7 +113,17 @@ class Direct_Stream():
if not chunk: if not chunk:
break break
# raise TunerError("807 - No Video Data") # raise TunerError("807 - No Video Data")
if key:
keyfile = self.fhdhr.web.session.get(key["url"]).content
cryptor = AES.new(keyfile, AES.MODE_CBC, keyfile)
chunk = cryptor.decrypt(chunk)
self.fhdhr.logger.info("Passing Through Chunk: %s" % chunkurl)
yield chunk yield chunk
if playlist.target_duration:
time.sleep(int(playlist.target_duration))
self.fhdhr.logger.info("Connection Closed: Tuner Lock Removed") self.fhdhr.logger.info("Connection Closed: Tuner Lock Removed")
except GeneratorExit: except GeneratorExit:

View File

@ -4,3 +4,5 @@ flask
image image
xmltodict xmltodict
sqlalchemy sqlalchemy
pycrypto
m3u8