From 22274859f07cc26f5ad884a2ef86030f8542ec59 Mon Sep 17 00:00:00 2001 From: Caitlin O'Callaghan Date: Tue, 29 Jun 2021 11:12:47 -0700 Subject: [PATCH 1/9] HTTP URL support for non-multiperiod inputs --- shaka-streamer | 3 +- streamer/controller_node.py | 57 ++++++++++++++++++++----------- streamer/packager_node.py | 64 ++++++++++++++++++++++------------- streamer/periodconcat_node.py | 16 ++++----- streamer/util.py | 20 +++++++++++ 5 files changed, 108 insertions(+), 52 deletions(-) create mode 100644 streamer/util.py diff --git a/shaka-streamer b/shaka-streamer index 6545098..5dde7fb 100755 --- a/shaka-streamer +++ b/shaka-streamer @@ -66,7 +66,8 @@ def main(): 'upload to. (Starts with gs:// or s3://)') parser.add_argument('-o', '--output', default='output_files', - help='The output folder to write files to. ' + + help='The output folder to write files to, or an HTTP ' + + 'or HTTPS URL where files will be PUT.' + 'Used even if uploading to cloud storage.') parser.add_argument('--skip_deps_check', action='store_true', diff --git a/streamer/controller_node.py b/streamer/controller_node.py index 090c1b3..3e99908 100644 --- a/streamer/controller_node.py +++ b/streamer/controller_node.py @@ -24,6 +24,7 @@ import os import re import shutil +import streamer.util import string import subprocess import sys @@ -96,7 +97,7 @@ def _create_pipe(self, suffix = '') -> str: return path - def start(self, output_dir: str, + def start(self, output_location: str, input_config_dict: Dict[str, Any], pipeline_config_dict: Dict[str, Any], bitrate_config_dict: Dict[Any, Any] = {}, @@ -135,15 +136,7 @@ def start(self, output_dir: str, if bucket_url: # If using cloud storage, make sure the user is logged in and can access # the destination, independent of the version check above. - CloudNode.check_access(bucket_url) - - - self._output_dir = output_dir - # Check if the directory for outputted Packager files exists, and if it - # does, delete it and remake a new one. - if os.path.exists(self._output_dir): - shutil.rmtree(self._output_dir) - os.mkdir(self._output_dir) + CloudNode.check_access(bucket_url) # Define resolutions and bitrates before parsing other configs. bitrate_config = BitrateConfig(bitrate_config_dict) @@ -157,29 +150,55 @@ def start(self, output_dir: str, self._input_config = InputConfig(input_config_dict) self._pipeline_config = PipelineConfig(pipeline_config_dict) + if not streamer.util.is_url(output_location): + # Check if the directory for outputted Packager files exists, and if it + # does, delete it and remake a new one. + if os.path.exists(output_location): + shutil.rmtree(output_location) + os.mkdir(output_location) + else : + # Check some restrictions and other details on HTTP output. + if not self._pipeline_config.segment_per_file: + raise RuntimeError( + 'For HTTP PUT uploads, the pipeline segment_per_file setting ' + + 'must be set to True!') + + if bucket_url: + raise RuntimeError( + 'Cloud bucket upload is incompatible with HTTP PUT support.') + + if self._input_config.multiperiod_inputs_list: + # TODO: Edit Multiperiod input list implementation to support HTTP outputs + raise RuntimeError( + 'Multiperiod input list support is incompatible with HTTP outputs.') + + # Note that we remove the trailing slash from the output location, because + # otherwise GCS would create a subdirectory whose name is "". + output_location = output_location.rstrip('/') + if self._input_config.inputs: # InputConfig contains inputs only. - self._append_nodes_for_inputs_list(self._input_config.inputs) + self._append_nodes_for_inputs_list(self._input_config.inputs, output_location) else: # InputConfig contains multiperiod_inputs_list only. # Create one Transcoder node and one Packager node for each period. for i, singleperiod in enumerate(self._input_config.multiperiod_inputs_list): sub_dir_name = 'period_' + str(i) - self._append_nodes_for_inputs_list(singleperiod.inputs, sub_dir_name) + self._append_nodes_for_inputs_list(singleperiod.inputs, output_location, sub_dir_name) if self._pipeline_config.streaming_mode == StreamingMode.VOD: packager_nodes = [node for node in self._nodes if isinstance(node, PackagerNode)] self._nodes.append(PeriodConcatNode( self._pipeline_config, packager_nodes, - self._output_dir)) + output_location)) if bucket_url: cloud_temp_dir = os.path.join(self._temp_dir, 'cloud') os.mkdir(cloud_temp_dir) packager_nodes = [node for node in self._nodes if isinstance(node, PackagerNode)] - self._nodes.append(CloudNode(self._output_dir, + self._nodes.append(CloudNode(output_location, bucket_url, cloud_temp_dir, packager_nodes, @@ -190,7 +209,7 @@ def start(self, output_dir: str, return self - def _append_nodes_for_inputs_list(self, inputs: List[Input], + def _append_nodes_for_inputs_list(self, inputs: List[Input], output_location: str, period_dir: Optional[str] = None) -> None: """A common method that creates Transcoder and Packager nodes for a list of Inputs passed to it. @@ -250,17 +269,15 @@ def _append_nodes_for_inputs_list(self, inputs: List[Input], self._nodes.append(TranscoderNode(inputs, self._pipeline_config, outputs)) - - output_dir = self._output_dir # If the inputs list was a period in multiperiod_inputs_list, create a nested directory # and put that period in it. if period_dir: - output_dir = os.path.join(output_dir, period_dir) - os.mkdir(output_dir) + output_location = os.path.join(output_location, period_dir) + os.mkdir(output_location) self._nodes.append(PackagerNode(self._pipeline_config, - output_dir, + output_location, outputs)) def check_status(self) -> ProcessStatus: diff --git a/streamer/packager_node.py b/streamer/packager_node.py index ebd69b3..c239dd9 100644 --- a/streamer/packager_node.py +++ b/streamer/packager_node.py @@ -15,6 +15,7 @@ """A module that feeds information from two named pipes into shaka-packager.""" import os +from streamer.util import is_url import subprocess from . import input_configuration @@ -33,38 +34,58 @@ INIT_SEGMENT = { - MediaType.AUDIO: '{dir}/audio_{language}_{channels}c_{bitrate}_{codec}_init.{format}', - MediaType.VIDEO: '{dir}/video_{resolution_name}_{bitrate}_{codec}_init.{format}', - MediaType.TEXT: '{dir}/text_{language}_init.{format}', + MediaType.AUDIO: 'audio_{language}_{channels}c_{bitrate}_{codec}_init.{format}', + MediaType.VIDEO: 'video_{resolution_name}_{bitrate}_{codec}_init.{format}', + MediaType.TEXT: 'text_{language}_init.{format}', } MEDIA_SEGMENT = { - MediaType.AUDIO: '{dir}/audio_{language}_{channels}c_{bitrate}_{codec}_$Number$.{format}', - MediaType.VIDEO: '{dir}/video_{resolution_name}_{bitrate}_{codec}_$Number$.{format}', - MediaType.TEXT: '{dir}/text_{language}_$Number$.{format}', + MediaType.AUDIO: 'audio_{language}_{channels}c_{bitrate}_{codec}_$Number$.{format}', + MediaType.VIDEO: 'video_{resolution_name}_{bitrate}_{codec}_$Number$.{format}', + MediaType.TEXT: 'text_{language}_$Number$.{format}', } SINGLE_SEGMENT = { - MediaType.AUDIO: '{dir}/audio_{language}_{channels}c_{bitrate}_{codec}.{format}', - MediaType.VIDEO: '{dir}/video_{resolution_name}_{bitrate}_{codec}.{format}', - MediaType.TEXT: '{dir}/text_{language}.{format}', + MediaType.AUDIO: 'audio_{language}_{channels}c_{bitrate}_{codec}.{format}', + MediaType.VIDEO: 'video_{resolution_name}_{bitrate}_{codec}.{format}', + MediaType.TEXT: 'text_{language}.{format}', } class SegmentError(Exception): """Raise when segment is incompatible with format.""" pass +def build_path(output_location, sub_path): + """Handle annoying edge cases with paths for cloud upload. + If a path has two slashes, GCS will create an intermediate directory named "". + So we have to be careful in how we construct paths to avoid this. + """ + # ControllerNode should have already stripped trailing slashes from the output + # location. + + # Sometimes the segment dir is empty. This handles that special case. + if not sub_path: + return output_location + + if is_url(output_location): + # Don't use os.path.join, since URLs must use forward slashes and Streamer + # could be used on Windows. + return output_location + '/' + sub_path + + return os.path.join(output_location, sub_path) + class PackagerNode(node_base.PolitelyWaitOnFinish): def __init__(self, pipeline_config: PipelineConfig, - output_dir: str, + output_location: str, output_streams: List[OutputStream]) -> None: super().__init__() self._pipeline_config: PipelineConfig = pipeline_config - self.output_dir: str = output_dir - self._segment_dir: str = os.path.join(output_dir, pipeline_config.segment_folder) + self.output_location: str = output_location + self._segment_dir: str = build_path( + output_location, pipeline_config.segment_folder) self._output_streams: List[OutputStream] = output_streams def start(self) -> None: @@ -140,16 +161,13 @@ def _setup_stream(self, stream: OutputStream) -> str: dict['language'] = stream.input.language if self._pipeline_config.segment_per_file: - dict['init_segment'] = stream.fill_template( - INIT_SEGMENT[stream.type], - dir=self._segment_dir) - dict['segment_template'] = stream.fill_template( - MEDIA_SEGMENT[stream.type], - dir=self._segment_dir) + dict['init_segment'] = build_path( + self._segment_dir, stream.fill_template(INIT_SEGMENT[stream.type])) + dict['segment_template'] = build_path( + self._segment_dir, stream.fill_template(MEDIA_SEGMENT[stream.type])) else: - dict['output'] = stream.fill_template( - SINGLE_SEGMENT[stream.type], - dir=self._segment_dir) + dict['output'] = build_path( + self._segment_dir, stream.fill_template(SINGLE_SEGMENT[stream.type])) if stream.is_dash_only(): dict['dash_only'] = '1' @@ -168,7 +186,7 @@ def _setup_manifest_format(self) -> List[str]: args += [ # Generate DASH manifest file. '--mpd_output', - os.path.join(self.output_dir, self._pipeline_config.dash_output), + os.path.join(self.output_location, self._pipeline_config.dash_output), ] if ManifestFormat.HLS in self._pipeline_config.manifest_format: if self._pipeline_config.streaming_mode == StreamingMode.LIVE: @@ -182,7 +200,7 @@ def _setup_manifest_format(self) -> List[str]: args += [ # Generate HLS playlist file(s). '--hls_master_playlist_output', - os.path.join(self.output_dir, self._pipeline_config.hls_output), + os.path.join(self.output_location, self._pipeline_config.hls_output), ] return args diff --git a/streamer/periodconcat_node.py b/streamer/periodconcat_node.py index 86c5224..24dd71b 100644 --- a/streamer/periodconcat_node.py +++ b/streamer/periodconcat_node.py @@ -26,17 +26,17 @@ class PeriodConcatNode(ThreadedNodeBase): """A node that concatenates multiple DASH manifests and/or HLS playlists - when the input is a multiperiod_inputs_list. + when the input is a multiperiod_inputs_list and the output is to the local the system. """ def __init__(self, pipeline_config: PipelineConfig, packager_nodes: List[PackagerNode], - output_dir: str) -> None: + output_location: str) -> None: """Stores all relevant information needed for the period concatenation.""" super().__init__(thread_name='periodconcat', continue_on_exception=False, sleep_time=3) self._pipeline_config = pipeline_config - self._output_dir = output_dir + self._output_location = output_location self._packager_nodes: List[PackagerNode] = packager_nodes def _thread_single_pass(self) -> None: @@ -81,7 +81,7 @@ def find(elem: ElementTree.Element, *args: str) -> ElementTree.Element: # Get the root of an MPD file that we will concatenate periods into. concat_mpd = ElementTree.ElementTree(file=os.path.join( - self._packager_nodes[0].output_dir, + self._packager_nodes[0].output_location, self._pipeline_config.dash_output)).getroot() # Get the default namespace. @@ -97,14 +97,14 @@ def find(elem: ElementTree.Element, *args: str) -> ElementTree.Element: for packager_node in self._packager_nodes: mpd = ElementTree.ElementTree(file=os.path.join( - packager_node.output_dir, + packager_node.output_location, self._pipeline_config.dash_output)).getroot() period = find(mpd, 'Period') period.attrib['duration'] = mpd.attrib['mediaPresentationDuration'] # A BaseURL that will have the relative path to media file. base_url = ElementTree.Element('BaseURL') - base_url.text = os.path.relpath(packager_node.output_dir, self._output_dir) + '/' + base_url.text = os.path.relpath(packager_node.output_location, self._output_location) + '/' period.insert(0, base_url) periods.append(period) @@ -112,9 +112,9 @@ def find(elem: ElementTree.Element, *args: str) -> ElementTree.Element: # Add the periods collected from all the files. concat_mpd.extend(periods) - # Write the period concat to the output_dir. + # Write the period concat to the output_location. with open(os.path.join( - self._output_dir, + self._output_location, self._pipeline_config.dash_output), 'w') as master_dash: contents = "\n" diff --git a/streamer/util.py b/streamer/util.py new file mode 100644 index 0000000..f1adcee --- /dev/null +++ b/streamer/util.py @@ -0,0 +1,20 @@ +# Copyright 2019 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Utility functions used by multiple modules.""" + +def is_url(output_location: str) -> bool: + """Returns True if the output location is a URL.""" + return (output_location.startswith('http:') or + output_location.startswith('https:')) \ No newline at end of file From 905eb2697d869bc2536198972ac80a6f7cce7bd4 Mon Sep 17 00:00:00 2001 From: Caitlin O'Callaghan Date: Tue, 29 Jun 2021 11:25:22 -0700 Subject: [PATCH 2/9] Unit tests for HTTP url support --- tests/tests.js | 46 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) diff --git a/tests/tests.js b/tests/tests.js index daf6e7b..240361a 100644 --- a/tests/tests.js +++ b/tests/tests.js @@ -13,13 +13,15 @@ // limitations under the License. const flaskServerUrl = 'http://localhost:5000/'; +const outputHttpUrl = 'http://0.0.0.0:8080/'; +const cloudUrl = "gs://my_gcs_bucket/folder/"; const dashManifestUrl = flaskServerUrl + 'output_files/dash.mpd'; const hlsManifestUrl = flaskServerUrl + 'output_files/hls.m3u8'; const TEST_DIR = 'test_assets/'; let player; let video; -async function startStreamer(inputConfig, pipelineConfig, bitrateConfig={}) { +async function startStreamer(inputConfig, pipelineConfig, bitrateConfig={}, outputLocation="", bucketUrl=null) { // Send a request to flask server to start Shaka Streamer. const response = await fetch(flaskServerUrl + 'start', { method: 'POST', @@ -30,6 +32,8 @@ async function startStreamer(inputConfig, pipelineConfig, bitrateConfig={}) { 'input_config': inputConfig, 'pipeline_config': pipelineConfig, 'bitrate_config': bitrateConfig, + 'output_location': outputLocation, + 'bucket_url': bucketUrl }), }); @@ -350,6 +354,46 @@ function errorTests() { field_name: 'inputs', })); }); + + it('fails when segment_per_file is false with a HTTP url output', async () => { + const inputConfig = getBasicInputConfig(); + const pipelineConfig = { + streaming_mode: 'live', + resolutions: [], + segment_per_file: false, + }; + + await expectAsync(startStreamer(inputConfig, pipelineConfig, {}, outputHttpUrl)) + .toBeRejected(); + }); + + it('fails when bucket_url is used with a HTTP url output', async () => { + const inputConfig = getBasicInputConfig(); + const pipelineConfig = { + streaming_mode: 'live', + resolutions: [], + segment_per_file: false, + }; + + await expectAsync(startStreamer(inputConfig, pipelineConfig, {}, outputHttpUrl, cloudUrl)) + .toBeRejected(); + }); + + it('fails when multiperiod_inputs_list is used with a HTTP url output', async () => { + const inputConfig = getBasicInputConfig(); + inputConfig.multiperiod_inputs_list = [ + getBasicInputConfig(), + getBasicInputConfig(), + ]; + const pipelineConfig = { + streaming_mode: 'live', + resolutions: [], + segment_per_file: false, + }; + + await expectAsync(startStreamer(inputConfig, pipelineConfig, {}, outputHttpUrl)) + .toBeRejected(); + }); } function resolutionTests(manifestUrl, format) { From fe0e2c12777011a9fd31264694664b762f4773cf Mon Sep 17 00:00:00 2001 From: Caitlin O'Callaghan Date: Tue, 29 Jun 2021 11:45:24 -0700 Subject: [PATCH 3/9] Add space to end of file --- streamer/util.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/streamer/util.py b/streamer/util.py index f1adcee..955d8ff 100644 --- a/streamer/util.py +++ b/streamer/util.py @@ -17,4 +17,5 @@ def is_url(output_location: str) -> bool: """Returns True if the output location is a URL.""" return (output_location.startswith('http:') or - output_location.startswith('https:')) \ No newline at end of file + output_location.startswith('https:')) + \ No newline at end of file From 4142eddfb9ccd36b0a8fdf47c3690efb152d360f Mon Sep 17 00:00:00 2001 From: Caitlin O'Callaghan Date: Tue, 29 Jun 2021 12:56:23 -0700 Subject: [PATCH 4/9] Add new line to end of file --- streamer/util.py | 1 - 1 file changed, 1 deletion(-) diff --git a/streamer/util.py b/streamer/util.py index 955d8ff..7b189d7 100644 --- a/streamer/util.py +++ b/streamer/util.py @@ -18,4 +18,3 @@ def is_url(output_location: str) -> bool: """Returns True if the output location is a URL.""" return (output_location.startswith('http:') or output_location.startswith('https:')) - \ No newline at end of file From e5a9fb1940cc76ef9dc82ba1e42c96b4a2d33a2d Mon Sep 17 00:00:00 2001 From: Caitlin O'Callaghan Date: Wed, 30 Jun 2021 16:32:29 -0700 Subject: [PATCH 5/9] Code cleanup --- streamer/controller_node.py | 6 +++--- streamer/packager_node.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/streamer/controller_node.py b/streamer/controller_node.py index 3e99908..326b255 100644 --- a/streamer/controller_node.py +++ b/streamer/controller_node.py @@ -24,7 +24,6 @@ import os import re import shutil -import streamer.util import string import subprocess import sys @@ -42,6 +41,7 @@ from streamer.pipeline_configuration import PipelineConfig, StreamingMode from streamer.transcoder_node import TranscoderNode from streamer.periodconcat_node import PeriodConcatNode +from streamer.util import is_url class ControllerNode(object): @@ -136,7 +136,7 @@ def start(self, output_location: str, if bucket_url: # If using cloud storage, make sure the user is logged in and can access # the destination, independent of the version check above. - CloudNode.check_access(bucket_url) + CloudNode.check_access(bucket_url) # Define resolutions and bitrates before parsing other configs. bitrate_config = BitrateConfig(bitrate_config_dict) @@ -150,7 +150,7 @@ def start(self, output_location: str, self._input_config = InputConfig(input_config_dict) self._pipeline_config = PipelineConfig(pipeline_config_dict) - if not streamer.util.is_url(output_location): + if not is_url(output_location): # Check if the directory for outputted Packager files exists, and if it # does, delete it and remake a new one. if os.path.exists(output_location): diff --git a/streamer/packager_node.py b/streamer/packager_node.py index c239dd9..f50844b 100644 --- a/streamer/packager_node.py +++ b/streamer/packager_node.py @@ -15,7 +15,6 @@ """A module that feeds information from two named pipes into shaka-packager.""" import os -from streamer.util import is_url import subprocess from . import input_configuration @@ -24,6 +23,7 @@ from streamer.output_stream import OutputStream from streamer.pipeline_configuration import EncryptionMode, PipelineConfig +from streamer.util import is_url from typing import List, Optional, Union # Alias a few classes to avoid repeating namespaces later. From 3969e56f3d45a00826027437484bffe2df7f268b Mon Sep 17 00:00:00 2001 From: Caitlin O'Callaghan Date: Tue, 6 Jul 2021 23:21:45 -0700 Subject: [PATCH 6/9] Correct HTTP output unit tests --- run_end_to_end_tests.py | 8 ++++++- streamer/controller_node.py | 2 +- tests/tests.js | 42 +++++++++++++------------------------ 3 files changed, 23 insertions(+), 29 deletions(-) diff --git a/run_end_to_end_tests.py b/run_end_to_end_tests.py index de81ede..f72b01e 100755 --- a/run_end_to_end_tests.py +++ b/run_end_to_end_tests.py @@ -155,7 +155,7 @@ def start(): controller = ControllerNode() try: - controller.start(OUTPUT_DIR, + controller.start(configs['output_location'], configs['input_config'], configs['pipeline_config'], configs['bitrate_config'], @@ -177,6 +177,12 @@ def start(): }) return createCrossOriginResponse( status=418, mimetype='application/json', body=body) + elif isinstance(e, RuntimeError): + body = json.dumps({ + 'error_type': 'RuntimeError', + }) + return createCrossOriginResponse( + status=418, mimetype='application/json', body=body) else: traceback.print_exc() return createCrossOriginResponse(status=500, body=str(e)) diff --git a/streamer/controller_node.py b/streamer/controller_node.py index 326b255..a39f647 100644 --- a/streamer/controller_node.py +++ b/streamer/controller_node.py @@ -156,7 +156,7 @@ def start(self, output_location: str, if os.path.exists(output_location): shutil.rmtree(output_location) os.mkdir(output_location) - else : + else: # Check some restrictions and other details on HTTP output. if not self._pipeline_config.segment_per_file: raise RuntimeError( diff --git a/tests/tests.js b/tests/tests.js index 240361a..83a2610 100644 --- a/tests/tests.js +++ b/tests/tests.js @@ -17,11 +17,12 @@ const outputHttpUrl = 'http://0.0.0.0:8080/'; const cloudUrl = "gs://my_gcs_bucket/folder/"; const dashManifestUrl = flaskServerUrl + 'output_files/dash.mpd'; const hlsManifestUrl = flaskServerUrl + 'output_files/hls.m3u8'; +const OUTPUT_DIR = 'output_files/' const TEST_DIR = 'test_assets/'; let player; let video; -async function startStreamer(inputConfig, pipelineConfig, bitrateConfig={}, outputLocation="", bucketUrl=null) { +async function startStreamer(inputConfig, pipelineConfig, bitrateConfig={}, outputLocation=OUTPUT_DIR) { // Send a request to flask server to start Shaka Streamer. const response = await fetch(flaskServerUrl + 'start', { method: 'POST', @@ -32,8 +33,7 @@ async function startStreamer(inputConfig, pipelineConfig, bitrateConfig={}, outp 'input_config': inputConfig, 'pipeline_config': pipelineConfig, 'bitrate_config': bitrateConfig, - 'output_location': outputLocation, - 'bucket_url': bucketUrl + 'output_location': outputLocation }), }); @@ -358,41 +358,29 @@ function errorTests() { it('fails when segment_per_file is false with a HTTP url output', async () => { const inputConfig = getBasicInputConfig(); const pipelineConfig = { - streaming_mode: 'live', + streaming_mode: 'vod', resolutions: [], segment_per_file: false, }; await expectAsync(startStreamer(inputConfig, pipelineConfig, {}, outputHttpUrl)) - .toBeRejected(); - }); - - it('fails when bucket_url is used with a HTTP url output', async () => { - const inputConfig = getBasicInputConfig(); - const pipelineConfig = { - streaming_mode: 'live', - resolutions: [], - segment_per_file: false, - }; - - await expectAsync(startStreamer(inputConfig, pipelineConfig, {}, outputHttpUrl, cloudUrl)) - .toBeRejected(); + .toBeRejectedWith(jasmine.objectContaining({ + error_type: 'RuntimeError', + })); }); it('fails when multiperiod_inputs_list is used with a HTTP url output', async () => { - const inputConfig = getBasicInputConfig(); - inputConfig.multiperiod_inputs_list = [ - getBasicInputConfig(), + const inputConfig = { + 'multiperiod_inputs_list': [ getBasicInputConfig(), - ]; - const pipelineConfig = { - streaming_mode: 'live', - resolutions: [], - segment_per_file: false, + ], }; - await expectAsync(startStreamer(inputConfig, pipelineConfig, {}, outputHttpUrl)) - .toBeRejected(); + + await expectAsync(startStreamer(inputConfig, minimalPipelineConfig, {}, outputHttpUrl)) + .toBeRejectedWith(jasmine.objectContaining({ + error_type: 'RuntimeError', + })); }); } From a142d0737fe0c311c713d2430dccffb3b1e130b9 Mon Sep 17 00:00:00 2001 From: Caitlin O'Callaghan Date: Tue, 6 Jul 2021 23:37:56 -0700 Subject: [PATCH 7/9] Code clean up --- tests/tests.js | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/tests.js b/tests/tests.js index 83a2610..6551fb7 100644 --- a/tests/tests.js +++ b/tests/tests.js @@ -14,7 +14,6 @@ const flaskServerUrl = 'http://localhost:5000/'; const outputHttpUrl = 'http://0.0.0.0:8080/'; -const cloudUrl = "gs://my_gcs_bucket/folder/"; const dashManifestUrl = flaskServerUrl + 'output_files/dash.mpd'; const hlsManifestUrl = flaskServerUrl + 'output_files/hls.m3u8'; const OUTPUT_DIR = 'output_files/' From 5768e5f9d1154d9b72e439b5c092ce7a1162278e Mon Sep 17 00:00:00 2001 From: Caitlin O'Callaghan Date: Tue, 6 Jul 2021 23:43:47 -0700 Subject: [PATCH 8/9] More cleanup --- tests/tests.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/tests.js b/tests/tests.js index 6551fb7..00ce53d 100644 --- a/tests/tests.js +++ b/tests/tests.js @@ -13,7 +13,7 @@ // limitations under the License. const flaskServerUrl = 'http://localhost:5000/'; -const outputHttpUrl = 'http://0.0.0.0:8080/'; +const outputHttpUrl = 'http://localhost:80/'; const dashManifestUrl = flaskServerUrl + 'output_files/dash.mpd'; const hlsManifestUrl = flaskServerUrl + 'output_files/hls.m3u8'; const OUTPUT_DIR = 'output_files/' @@ -375,7 +375,6 @@ function errorTests() { ], }; - await expectAsync(startStreamer(inputConfig, minimalPipelineConfig, {}, outputHttpUrl)) .toBeRejectedWith(jasmine.objectContaining({ error_type: 'RuntimeError', From a3610bdcc927a912f24ae13ae29a7c527c4c7b7c Mon Sep 17 00:00:00 2001 From: Caitlin O'Callaghan Date: Wed, 7 Jul 2021 16:47:22 -0700 Subject: [PATCH 9/9] Fix indentation --- tests/tests.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/tests.js b/tests/tests.js index 00ce53d..9556aa3 100644 --- a/tests/tests.js +++ b/tests/tests.js @@ -371,7 +371,7 @@ function errorTests() { it('fails when multiperiod_inputs_list is used with a HTTP url output', async () => { const inputConfig = { 'multiperiod_inputs_list': [ - getBasicInputConfig(), + getBasicInputConfig(), ], };