def stream_direct(self, _channel_dict, _write_buffer): """ Processes m3u8 interface without using ffmpeg """ self.config = self.db_configdefn.get_config() self.channel_dict = _channel_dict self.write_buffer = _write_buffer duration = 6 play_queue = OrderedDict() self.last_refresh = time.time() stream_uri = self.get_stream_uri(_channel_dict) if not stream_uri: self.logger.warning('Unknown Channel') return self.logger.debug('M3U8: {}'.format(stream_uri)) self.file_filter = None if self.config[_channel_dict['namespace'].lower( )]['player-enable_url_filter']: stream_filter = self.config[ _channel_dict['namespace'].lower()]['player-url_filter'] if stream_filter is not None: self.file_filter = re.compile(stream_filter) else: self.logger.warning( '[{}]][player-enable_url_filter]' ' enabled but [player-url_filter] not set'.format( _channel_dict['namespace'].lower())) if self.config[_channel_dict['namespace'].lower( )]['player-enable_pts_filter']: self.pts_validation = PTSValidation(self.config, self.channel_dict) while True: try: added = 0 removed = 0 playlist = m3u8.load(stream_uri) removed += self.remove_from_stream_queue(playlist, play_queue) added += self.add_to_stream_queue(playlist, play_queue) if added == 0 and duration > 0: time.sleep(duration * 0.3) elif self.plugins.plugins[_channel_dict['namespace']].plugin_obj \ .is_time_to_refresh_ext(self.last_refresh, _channel_dict['instance']): stream_uri = self.get_stream_uri(_channel_dict) self.logger.debug('M3U8: {}'.format(stream_uri)) self.last_refresh = time.time() self.play_queue(play_queue) except IOError as e: # Check we hit a broken pipe when trying to write back to the client if e.errno in [ errno.EPIPE, errno.ECONNABORTED, errno.ECONNRESET, errno.ECONNREFUSED ]: # Normal process. Client request end of stream self.logger.info( '2. Connection dropped by end device {}'.format(e)) break else: self.logger.error('{}{}'.format('3 UNEXPECTED EXCEPTION=', e)) raise
def get_channel_uri(self, _channel_id): self.logger.info(self.locast.name + ": Getting station info for " + _channel_id) stream_url = ''.join([ 'https://api.locastnet.org/api/watch/station/', str(_channel_id), '/', self.locast_instance.location.latitude, '/', self.locast_instance.location.longitude]) stream_headers = {'Content-Type': 'application/json', 'authorization': 'Bearer ' + self.locast_instance.token, 'User-agent': constants.DEFAULT_USER_AGENT} req = urllib.request.Request(stream_url, headers=stream_headers) with urllib.request.urlopen(req) as resp: stream_result = json.load(resp) self.logger.debug("Determining best video stream for " + _channel_id + "...") bestStream = None # find the heighest stream url resolution and save it to the list videoUrlM3u = m3u8.load(stream_result['streamUrl'], headers={'authorization': 'Bearer ' + self.locast_instance.token, 'User-agent': constants.DEFAULT_USER_AGENT}) self.logger.debug("Found " + str(len(videoUrlM3u.playlists)) + " Playlists") if len(videoUrlM3u.playlists) > 0: for videoStream in videoUrlM3u.playlists: if bestStream is None: bestStream = videoStream elif ((videoStream.stream_info.resolution[0] > bestStream.stream_info.resolution[0]) and (videoStream.stream_info.resolution[1] > bestStream.stream_info.resolution[1])): bestStream = videoStream elif ((videoStream.stream_info.resolution[0] == bestStream.stream_info.resolution[0]) and (videoStream.stream_info.resolution[1] == bestStream.stream_info.resolution[1]) and (videoStream.stream_info.bandwidth > bestStream.stream_info.bandwidth)): bestStream = videoStream if bestStream is not None: self.logger.debug(_channel_id + " will use " + str(bestStream.stream_info.resolution[0]) + "x" + str(bestStream.stream_info.resolution[1]) + " resolution at " + str(bestStream.stream_info.bandwidth) + "bps") return bestStream.absolute_uri else: self.logger.debug("No variant streams found for this station. Assuming single stream only.") return stream_result['streamUrl']
def get_station_stream_uri(self, station_id): print("Getting station info for " + station_id + "...") try: videoUrlReq = urllib.request.Request('https://api.locastnet.org/api/watch/station/' + str(station_id) + '/' + self.location['latitude'] + '/' + self.location['longitude'], headers={'Content-Type': 'application/json', 'authorization': 'Bearer ' + self.current_token, 'User-agent': self.DEFAULT_USER_AGENT}) videoUrlOpn = urllib.request.urlopen(videoUrlReq) videoUrlRes = json.load(videoUrlOpn) videoUrlOpn.close() except urllib.error.URLError as urlError: print("Error when getting the video URL: " + str(urlError.reason)) return False except urllib.error.HTTPError as httpError: print("Error when getting the video URL: " + str(httpError.reason)) return False except: videoUrlReqErr = sys.exc_info()[0] if hasattr(videoUrlReqErr, 'message'): print("Error when getting the video URL: " + videoUrlReqErr.message) elif hasattr(videoUrlReqErr, 'reason'): print("Error when getting the video URL: " + videoUrlReqErr.reason) else: print("Error when getting the video URL: " + str(videoUrlReqErr)) return False print("Determining best video stream for " + station_id + "...") bestStream = None # find the heighest stream url resolution and save it to the list videoUrlM3u = m3u8.load(videoUrlRes['streamUrl'], headers={'authorization': 'Bearer ' + self.current_token, 'User-agent': self.DEFAULT_USER_AGENT}) print("Found " + str(len(videoUrlM3u.playlists)) + " Playlists") if len(videoUrlM3u.playlists) > 0: for videoStream in videoUrlM3u.playlists: if bestStream is None: bestStream = videoStream elif ((videoStream.stream_info.resolution[0] > bestStream.stream_info.resolution[0]) and (videoStream.stream_info.resolution[1] > bestStream.stream_info.resolution[1])): bestStream = videoStream elif ((videoStream.stream_info.resolution[0] == bestStream.stream_info.resolution[0]) and (videoStream.stream_info.resolution[1] == bestStream.stream_info.resolution[1]) and (videoStream.stream_info.bandwidth > bestStream.stream_info.bandwidth)): bestStream = videoStream if bestStream is not None: print(station_id + " will use " + str(bestStream.stream_info.resolution[0]) + "x" + str(bestStream.stream_info.resolution[1]) + " resolution at " + str(bestStream.stream_info.bandwidth) + "bps") return bestStream.absolute_uri else: print("No variant streams found for this station. Assuming single stream only.") return videoUrlRes['streamUrl']
def stream_direct(self, sid, _namespace, _instance, station_list): segments = OrderedDict() duration = 1 file_filter = None self.last_refresh = time.time() stream_uri = self.get_stream_uri(sid, _namespace, _instance) if not stream_uri: self.do_response( 501, 'text/html', tvh_templates['htmlError'].format('501 - Unknown channel')) return self.logger.debug('M3U8: {}'.format(stream_uri)) if self.config['player']['stream_filter'] is not None: file_filter = re.compile(self.config['player']['stream_filter']) while True: try: added = 0 removed = 0 playlist = m3u8.load(stream_uri) for segment_dict in list(segments.keys()): is_found = False for segment_m3u8 in playlist.segments: uri = segment_m3u8.absolute_uri if segment_dict == uri: is_found = True break if not is_found: del segments[segment_dict] removed += 1 self.logger.debug( f"Removed {segment_dict} from play queue") continue else: break for m3u8_segment in playlist.segments: uri = m3u8_segment.absolute_uri if uri not in segments: played = False if file_filter is not None: m = file_filter.match(uri) if m: played = True segments[uri] = { 'played': played, 'duration': m3u8_segment.duration } self.logger.debug(f"Added {uri} to play queue") added += 1 if added == 0 and duration > 0: time.sleep(duration * 0.3) elif self.is_time_to_refresh(): stream_uri = self.get_stream_uri(sid, _namespace, _instance) self.logger.debug('M3U8: {}'.format(stream_uri)) self.last_refresh = time.time() for uri, data in segments.items(): if not data["played"]: start_download = datetime.datetime.utcnow() chunk = requests.get(uri).content end_download = datetime.datetime.utcnow() download_secs = (end_download - start_download).total_seconds() data['played'] = True if not chunk: self.logger.warning( f"Segment {uri} not available. Skipping..") continue atsc_msg = ATSCMsg() chunk_updated = atsc_msg.update_sdt_names( chunk[:80], b'Locast', self.set_service_name(station_list, sid).encode()) chunk = chunk_updated + chunk[80:] duration = data['duration'] runtime = (datetime.datetime.utcnow() - start_download).total_seconds() target_diff = 0.3 * duration wait = target_diff - runtime self.logger.info(f"Serving {uri} ({duration}s)") self.write_buffer(chunk) if wait > 0: time.sleep(wait) except IOError as e: # Check we hit a broken pipe when trying to write back to the client if e.errno in [ errno.EPIPE, errno.ECONNABORTED, errno.ECONNRESET, errno.ECONNREFUSED ]: # Normal process. Client request end of stream self.logger.info( '2. Connection dropped by end device {}'.format(e)) break else: self.logger.error('{}{}'.format('3 UNEXPECTED EXCEPTION=', e)) raise except Exception as e: traceback.print_exc() break