diff --git a/build/all.py b/build/all.py index b121f313a..731dcbba2 100755 --- a/build/all.py +++ b/build/all.py @@ -17,10 +17,14 @@ """Builds the dependencies, runs the checks, and compiles the library.""" import argparse + +import apps import build import check +import compiler import docs import gendeps +import os import shakaBuildHelpers @@ -54,24 +58,36 @@ def main(args): parsed_args = parser.parse_args(args) - code = gendeps.gen_deps([]) - if code != 0: - return code + if gendeps.main([]) != 0: + return 1 - check_args = ['--fix'] if parsed_args.fix else [] - code = check.main(check_args) - if code != 0: - return code + check_args = [] + if parsed_args.fix: + check_args += ['--fix'] + if parsed_args.force: + check_args += ['--force'] + if check.main(check_args) != 0: + return 1 docs_args = [] - code = docs.build_docs(docs_args) - if code != 0: - return code + if parsed_args.force: + docs_args += ['--force'] + if docs.main(docs_args) != 0: + return 1 - build_args = ['--name', 'compiled', '+@complete'] + src = os.path.join(shakaBuildHelpers.get_source_base(), 'ui', 'controls.less') + output = os.path.join( + shakaBuildHelpers.get_source_base(), 'dist', 'controls.css') + less = compiler.Less([src], output) + if not less.compile(parsed_args.force): + return 1 + + build_args_with_ui = ['--name', 'ui', '+@complete'] + build_args_without_ui = ['--name', 'compiled', '+@complete', '-@ui'] if parsed_args.force: - build_args += ['--force'] + build_args_with_ui += ['--force'] + build_args_without_ui += ['--force'] # Create the list of build modes to build with. If the list is empty # by the end, then populate it with every mode. @@ -83,16 +99,19 @@ def main(args): if not modes: modes += ['debug', 'release'] - result = 0 - for mode in modes: - result = build.main(build_args + ['--mode', mode]) + # Complete build includes the UI library, but it is optional and player lib + # should build and work without it as well. + # First, build the full build (UI included) and then build excluding UI. + for build_args in [build_args_with_ui, build_args_without_ui]: + if build.main(build_args + ['--mode', mode]) != 0: + return 1 - # If a build fails then there is no reason to build the other modes. - if result: - break + is_debug = mode == 'debug' + if not apps.build_all(parsed_args.force, is_debug): + return 1 - return result + return 0 if __name__ == '__main__': shakaBuildHelpers.run_main(main) diff --git a/build/apps.py b/build/apps.py new file mode 100755 index 000000000..2c1ec41f8 --- /dev/null +++ b/build/apps.py @@ -0,0 +1,170 @@ +#!/usr/bin/env python +# +# Copyright 2016 Google Inc. All Rights Reserved. +# +# 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 +# +# http://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. + +"""Build our various applications.""" + +import argparse +import logging +import os +import re + +import build +import compiler +import shakaBuildHelpers + + +def compile_demo(force, is_debug): + """Compile the demo application. + + Args: + force: True to rebuild, False to ignore if no changes are detected. + is_debug: True to compile for debugging, false for release. + + Returns: + True on success, False on failure. + """ + logging.info('Compiling the demo app (%s)...', + 'debug' if is_debug else 'release') + + match = re.compile(r'.*\.js$') + base = shakaBuildHelpers.get_source_base() + def get(path): + return shakaBuildHelpers.get_all_files(os.path.join(base, path), match) + + files = set(get('demo') + get('externs')) - set(get('demo/cast_receiver')) + # Make sure we don't compile in load.js, which will be used to bootstrap + # everything else. If we build that into the output, we will get an + # infinite loop of scripts adding themselves. + files.remove(os.path.join(base, 'demo', 'load.js')) + # Remove service_worker.js as well. This executes in a different context. + files.remove(os.path.join(base, 'demo', 'service_worker.js')) + + # Add in the generated externs, so that the demo compilation knows the + # definitions of the library APIs. + externs = ('shaka-player.ui.debug.externs.js' if is_debug + else 'shaka-player.ui.externs.js') + + files.add(os.path.join(base, 'dist', externs)) + + name = 'demo.compiled' + ('.debug' if is_debug else '') + + # TODO: Why do we always use debug_closure_opts? Nobody remembers. + closure_opts = build.common_closure_opts + build.debug_closure_opts + closure_opts += [ + # Ignore missing goog.require since we assume the whole library is + # already included. + '--jscomp_off=missingRequire', '--jscomp_off=strictMissingRequire', + '-D', 'COMPILED=true', + ] + + closure = compiler.ClosureCompiler(files, name) + # The output wrapper is only designed for the library. It can't be used + # for apps. TODO: Should we add a simple function wrapper for apps? + closure.add_wrapper = False + if not closure.compile(closure_opts, force): + return False + + return True + + +def compile_receiver(force, is_debug): + """Compile the cast receiver application. + + Args: + force: True to rebuild, False to ignore if no changes are detected. + is_debug: True to compile for debugging, false for release. + + Returns: + True on success, False on failure. + """ + logging.info('Compiling the receiver app (%s)...', + 'debug' if is_debug else 'release') + + match = re.compile(r'.*\.js$') + base = shakaBuildHelpers.get_source_base() + def get(path): + return shakaBuildHelpers.get_all_files(os.path.join(base, path), match) + + files = set(get('demo/common') + + get('demo/cast_receiver') + + get('externs') + get('lib/debug') + + get('third_party/closure')) + + # Add in the generated externs, so that the receiver compilation knows the + # definitions of the library APIs. + externs = ('shaka-player.ui.debug.externs.js' if is_debug + else 'shaka-player.ui.externs.js') + + files.add(os.path.join(base, 'dist', externs)) + + name = 'receiver.compiled' + ('.debug' if is_debug else '') + + # TODO: Why do we always use debug_closure_opts? Nobody remembers. + closure_opts = build.common_closure_opts + build.debug_closure_opts + closure_opts += [ + # Ignore missing goog.require since we assume the whole library is + # already included. + '--jscomp_off=missingRequire', '--jscomp_off=strictMissingRequire', + '-D', 'COMPILED=true', + ] + + closure = compiler.ClosureCompiler(files, name) + # The output wrapper is only designed for the library. It can't be used + # for apps. TODO: Should we add a simple function wrapper for apps? + closure.add_wrapper = False + if not closure.compile(closure_opts, force): + return False + + return True + + +def build_all(force, is_debug): + if not compile_demo(force, is_debug): + return False + + if not compile_receiver(force, is_debug): + return False + + return True + + +def main(args): + parser = argparse.ArgumentParser( + description=__doc__, + formatter_class=argparse.RawDescriptionHelpFormatter) + + parser.add_argument( + '--force', + '-f', + help='Force building the library even if no files have changed.', + action='store_true') + + parsed_args = parser.parse_args(args) + force = parsed_args.force + + # Update node modules if needed. + if not shakaBuildHelpers.update_node_modules(): + return 1 + + for is_debug in [True, False]: + if not build_all(force, is_debug): + return 1 + + return 0 + + +if __name__ == '__main__': + shakaBuildHelpers.run_main(main) diff --git a/build/build.py b/build/build.py index d020501e6..b88f093ae 100755 --- a/build/build.py +++ b/build/build.py @@ -24,11 +24,12 @@ Build files are the files found in build/types. These files are simply a newline separated list of commands to execute. So if the "+@complete" command is given, it will open the complete file and run it (which may in turn open other build files). Subtracting a build file will reverse all actions applied -by the given file. So "-@networking" will remove all the networking plugins. +by the given file. So "-@networking" will remove all the networking plugins, +and "-@ui" will remove the UI. The core library is always included so does not have to be listed. The default -is to use the name 'compiled'; if no commands are given, it will build the -complete build. +is to use the name 'ui'; if no commands are given, it will build the complete +build, including the UI. Examples: # Equivalent to +@complete @@ -36,6 +37,7 @@ Examples: build.py +@complete build.py +@complete -@networking + build.py +@complete -@ui build.py --name custom +@manifests +@networking +../my_plugin.js """ @@ -43,9 +45,8 @@ import argparse import logging import os import re -import subprocess -import sys +import compiler import shakaBuildHelpers @@ -82,9 +83,8 @@ debug_closure_defines = [ '-D', 'goog.DEBUG=true', '-D', 'goog.asserts.ENABLE_ASSERTS=true', '-D', 'shaka.log.MAX_LOG_LEVEL=4', # shaka.log.Level.DEBUG - '-D', 'shaka.Player.version="%s-debug"' % ( - shakaBuildHelpers.calculate_version()), ] + release_closure_opts = [ '-O', 'ADVANCED', ] @@ -92,7 +92,6 @@ release_closure_defines = [ '-D', 'goog.DEBUG=false', '-D', 'goog.asserts.ENABLE_ASSERTS=false', '-D', 'shaka.log.MAX_LOG_LEVEL=0', - '-D', 'shaka.Player.version="%s"' % shakaBuildHelpers.calculate_version(), ] @@ -142,16 +141,11 @@ class Build(object): self.include = include_all - exclude_all self.exclude = exclude_all - include_all - def _get_closure_jar_path(self): - jar = os.path.join(shakaBuildHelpers.get_source_base(), - 'third_party', 'closure', 'compiler.jar') - return shakaBuildHelpers.cygwin_safe_path(jar) - def reverse(self): return Build(self.exclude, self.include) - def add_core(self): - """Adds the core library.""" + def add_closure(self): + """Adds the closure library and externs.""" # Add externs and closure dependencies. source_base = shakaBuildHelpers.get_source_base() match = re.compile(r'.*\.js$') @@ -161,6 +155,8 @@ class Build(object): shakaBuildHelpers.get_all_files( os.path.join(source_base, 'third_party', 'closure'), match)) + def add_core(self): + """Adds the core library.""" # Check that there are no files in 'core' that are removed core_build = Build() core_build.parse_build(['+@core'], os.getcwd()) @@ -237,273 +233,43 @@ class Build(object): return True - def build_raw(self, closure_opts): - """Builds the files in |self.include| using the given extra Closure options. - - Args: - closure_opts: An array of options to give to Closure. - - Returns: - True on success; False on failure. - """ - jar = self._get_closure_jar_path() - files = [shakaBuildHelpers.cygwin_safe_path(f) for f in self.include] - files.sort() - - cmd_line = ['java', '-jar', jar] + closure_opts + files - if shakaBuildHelpers.execute_get_code(cmd_line) != 0: - logging.error('Build failed') - return False - - return True - - def generate_externs(self, name): - """Generates externs for the files in |self.include|. - - Args: - name: The name of the build. - - Returns: - True on success; False on failure. - """ - files = [shakaBuildHelpers.cygwin_safe_path(f) for f in self.include] - - extern_generator = shakaBuildHelpers.cygwin_safe_path(os.path.join( - shakaBuildHelpers.get_source_base(), 'build', 'generateExterns.js')) - - output = shakaBuildHelpers.cygwin_safe_path(os.path.join( - shakaBuildHelpers.get_source_base(), 'dist', - 'shaka-player.' + name + '.externs.js')) - - cmd_line = ['node', extern_generator, '--output', output] + files - if shakaBuildHelpers.execute_get_code(cmd_line) != 0: - logging.error('Externs generation failed') - return False - - return True - - def build_library(self, name, rebuild, is_debug): + def build_library(self, name, force, is_debug): """Builds Shaka Player using the files in |self.include|. Args: name: The name of the build. - rebuild: True to rebuild, False to ignore if no changes are detected. + force: True to rebuild, False to ignore if no changes are detected. is_debug: True to compile for debugging, false for release. Returns: True on success; False on failure. """ + self.add_closure() self.add_core() - # In the build files, we use '/' in the paths, however Windows uses '\'. - # Although Windows supports both, the source mapping will not work. So - # use Linux-style paths for arguments. - source_base = shakaBuildHelpers.get_source_base().replace('\\', '/') if is_debug: name += '.debug' - result_file, result_map = compute_output_files('shaka-player.' + name) - - # Don't build if we don't have to. - if not rebuild and not self.should_build(result_file): - return True + build_name = 'shaka-player.' + name + closure = compiler.ClosureCompiler(self.include, build_name) + generator = compiler.ExternGenerator(self.include, build_name) closure_opts = common_closure_opts + common_closure_defines if is_debug: closure_opts += debug_closure_opts + debug_closure_defines + # The output wrapper is only used in the release build. + closure.add_wrapper = False else: closure_opts += release_closure_opts + release_closure_defines - # The output wrapper is only used in the release build. - closure_opts += self.add_wrapper() - closure_opts += [ - '--create_source_map', result_map, '--js_output_file', result_file, - '--source_map_location_mapping', source_base + '|..' - ] - if not self.build_raw(closure_opts): + if not closure.compile(closure_opts, force): return False - self.add_source_map(result_file, result_map) - - if not self.generate_externs(name): + if not generator.generate(force): return False return True - def add_source_map(self, result_file, result_map): - # Add a special source-mapping comment so that Chrome and Firefox can map - # line and character numbers from the compiled library back to the original - # source locations. - with open(result_file, 'a') as f: - f.write('//# sourceMappingURL=%s' % os.path.basename(result_map)) - - def add_wrapper(self): - """Prepares an output wrapper and returns a list of command line arguments - for Closure Compiler to use it.""" - - # Load the wrapper and use Closure to strip whitespace and comments. - # This requires %output% in the template to be protected, so Closure doesn't - # fail to parse it. - base = shakaBuildHelpers.cygwin_safe_path( - shakaBuildHelpers.get_source_base()) - wrapper_input_path = '%s/build/wrapper.template.js' % base - wrapper_output_path = '%s/dist/wrapper.js' % base - - with open(wrapper_input_path, 'rb') as f: - wrapper_code = f.read().decode('utf8').replace('%output%', '"%output%"') - - jar = self._get_closure_jar_path() - cmd_line = ['java', '-jar', jar, '-O', 'WHITESPACE_ONLY'] - proc = shakaBuildHelpers.execute_subprocess( - cmd_line, stdin=subprocess.PIPE, stdout=subprocess.PIPE, - stderr=subprocess.PIPE) - stripped_wrapper_code = proc.communicate(wrapper_code.encode('utf8'))[0] - - if proc.returncode != 0: - raise RuntimeError('Failed to strip whitespace from wrapper!') - - with open(wrapper_output_path, 'wb') as f: - code = stripped_wrapper_code.decode('utf8') - f.write(code.replace('"%output%"', '%output%').encode('utf8')) - - return ['--output_wrapper_file=%s' % wrapper_output_path] - - def should_build(self, result_file): - if not os.path.isfile(result_file): - # Nothing built, so we should definitely build. - return True - - # Detect changes to the set of files that we intend to build. - build_time = os.path.getmtime(result_file) - # Get a list of files modified since the result file was created. - edited_files = [f for f in self.include if os.path.getmtime(f) > build_time] - if edited_files: - # Some input files have changed, so we should build again. - return True - - logging.warning('No changes detected, not building. Use --force ' - 'to override.') - return False - - -def compute_output_files(base_name): - source_base = shakaBuildHelpers.get_source_base().replace('\\', '/') - prefix = shakaBuildHelpers.cygwin_safe_path( - os.path.join(source_base, 'dist', base_name)) - js_path = prefix + '.js' - map_path = prefix + '.map' - return js_path, map_path - - -def compile_demo(rebuild, is_debug): - """Compile the demo application. - - Args: - rebuild: True to rebuild, False to ignore if no changes are detected. - is_debug: True to compile for debugging, false for release. - - Returns: - True on success, False on failure. - """ - logging.info('Compiling the demo app (%s)...', - 'debug' if is_debug else 'release') - - match = re.compile(r'.*\.js$') - base = shakaBuildHelpers.get_source_base() - def get(*args): - return shakaBuildHelpers.get_all_files(os.path.join(base, *args), match) - - files = set(get('demo') + get('externs')) - set(get('demo/cast_receiver')) - # Make sure we don't compile in load.js, which will be used to bootstrap - # everything else. If we build that into the output, we will get an infinite - # loop of scripts adding themselves. - files.remove(os.path.join(base, 'demo', 'load.js')) - # Remove service_worker.js as well. This executes in a different context. - files.remove(os.path.join(base, 'demo', 'service_worker.js')) - # Add in the generated externs, so that the demo compilation knows the - # definitions of the library APIs. - extern_name = ('shaka-player.compiled.debug.externs.js' if is_debug - else 'shaka-player.compiled.externs.js') - files.add(os.path.join(base, 'dist', extern_name)) - - demo_build = Build(files) - - name = 'demo.compiled' + ('.debug' if is_debug else '') - result_file, result_map = compute_output_files(name) - - # Don't build if we don't have to. - if not rebuild and not demo_build.should_build(result_file): - return True - - source_base = shakaBuildHelpers.get_source_base().replace('\\', '/') - closure_opts = common_closure_opts + debug_closure_opts - closure_opts += [ - # Ignore missing goog.require since we assume the whole library is - # already included. - '--jscomp_off=missingRequire', '--jscomp_off=strictMissingRequire', - '--create_source_map', result_map, '--js_output_file', result_file, - '--source_map_location_mapping', source_base + '|..', - '-D', 'COMPILED=true', - ] - - if not demo_build.build_raw(closure_opts): - return False - - demo_build.add_source_map(result_file, result_map) - return True - - -def compile_receiver(rebuild, is_debug): - """Compile the cast receiver application. - - Args: - rebuild: True to rebuild, False to ignore if no changes are detected. - is_debug: True to compile for debugging, false for release. - - Returns: - True on success, False on failure. - """ - logging.info('Compiling the receiver app (%s)...', - 'debug' if is_debug else 'release') - - match = re.compile(r'.*\.js$') - base = shakaBuildHelpers.get_source_base() - def get(*args): - return shakaBuildHelpers.get_all_files(os.path.join(base, *args), match) - - files = set(get('demo/common') + get('demo/cast_receiver') + get('externs')) - # Add in the generated externs, so that the receiver compilation knows the - # definitions of the library APIs. - extern_name = ('shaka-player.compiled.debug.externs.js' if is_debug - else 'shaka-player.compiled.externs.js') - files.add(os.path.join(base, 'dist', extern_name)) - - receiver_build = Build(files) - - name = 'receiver.compiled' + ('.debug' if is_debug else '') - result_file, result_map = compute_output_files(name) - - # Don't build if we don't have to. - if not rebuild and not receiver_build.should_build(result_file): - return True - - source_base = shakaBuildHelpers.get_source_base().replace('\\', '/') - closure_opts = common_closure_opts + debug_closure_opts - closure_opts += [ - # Ignore missing goog.require since we assume the whole library is - # already included. - '--jscomp_off=missingRequire', '--jscomp_off=strictMissingRequire', - '--create_source_map', result_map, '--js_output_file', result_file, - '--source_map_location_mapping', source_base + '|..', - '-D', 'COMPILED=true', - ] - - if not receiver_build.build_raw(closure_opts): - return False - - receiver_build.add_source_map(result_file, result_map) - return True - def main(args): parser = argparse.ArgumentParser( @@ -531,9 +297,9 @@ def main(args): parser.add_argument( '--name', - help='Set the name of the build. Uses "compiled" if not given.', + help='Set the name of the build. Uses "ui" if not given.', type=str, - default='compiled') + default='ui') parsed_args, commands = parser.parse_known_args(args) @@ -541,7 +307,8 @@ def main(args): if len(commands) == 0: commands.append('+@complete') - logging.info('Compiling the library (%s)...', parsed_args.mode) + logging.info('Compiling the library (%s, %s)...', + parsed_args.name, parsed_args.mode) custom_build = Build() @@ -553,19 +320,14 @@ def main(args): return 1 name = parsed_args.name - rebuild = parsed_args.force + force = parsed_args.force is_debug = parsed_args.mode == 'debug' - if not custom_build.build_library(name, rebuild, is_debug): - return 1 - - if not compile_demo(rebuild, is_debug): - return 1 - - if not compile_receiver(rebuild, is_debug): + if not custom_build.build_library(name, force, is_debug): return 1 return 0 + if __name__ == '__main__': shakaBuildHelpers.run_main(main) diff --git a/build/check.py b/build/check.py index 47317cc75..1b3d7fb66 100755 --- a/build/check.py +++ b/build/check.py @@ -26,9 +26,9 @@ import argparse import logging import os import re -import sys import build +import compiler import shakaBuildHelpers @@ -38,35 +38,32 @@ def get_lint_files(): base = shakaBuildHelpers.get_source_base() def get(arg): return shakaBuildHelpers.get_all_files(os.path.join(base, arg), match) - return get('test') + get('lib') + get('externs') + get('demo') + return get('test') + get('lib') + get('externs') + get('demo') + get('ui') def check_js_lint(args): """Runs the JavaScript linter.""" # TODO: things not enforced: property doc requirements - logging.info('Running eslint...') + logging.info('Linting JavaScript...') - eslint = shakaBuildHelpers.get_node_binary('eslint') - cmd_line = eslint + get_lint_files() - if args.fix: - cmd_line += ['--fix'] - return shakaBuildHelpers.execute_get_code(cmd_line) == 0 + base = shakaBuildHelpers.get_source_base() + config_path = os.path.join(base, '.eslintrc.js') + + linter = compiler.Linter(get_lint_files(), config_path) + return linter.lint(fix=args.fix, force=args.force) -def check_html_lint(_): - """Runs the HTML linter over the HTML files. +def check_html_lint(args): + """Runs the HTML linter.""" + logging.info('Linting HTML...') - Returns: - True on success, False on failure. - """ - logging.info('Running htmlhint...') - htmlhint = shakaBuildHelpers.get_node_binary('htmlhint') base = shakaBuildHelpers.get_source_base() files = ['index.html', 'demo/index.html', 'support.html'] file_paths = [os.path.join(base, x) for x in files] config_path = os.path.join(base, '.htmlhintrc') - cmd_line = htmlhint + ['--config=' + config_path] + file_paths - return shakaBuildHelpers.execute_get_code(cmd_line) == 0 + + htmllinter = compiler.HtmlLinter(file_paths, config_path) + return htmllinter.lint(force=args.force) def check_complete(_): @@ -102,7 +99,7 @@ def check_complete(_): return True -def check_tests(_): +def check_tests(args): """Runs an extra compile pass over the test code to check for type errors. Returns: @@ -112,12 +109,12 @@ def check_tests(_): match = re.compile(r'.*\.js$') base = shakaBuildHelpers.get_source_base() - def get(*args): - return shakaBuildHelpers.get_all_files(os.path.join(base, *args), match) - files = set(get('lib') + get('externs') + get('test') + - get('third_party', 'closure')) - files.add(os.path.join(base, 'demo', 'common', 'assets.js')) - test_build = build.Build(files) + def get(path): + return shakaBuildHelpers.get_all_files(os.path.join(base, path), match) + files = set(get('lib') + get('externs') + get('test') + get('ui') + + get('third_party/closure') + + get('third_party/language-mapping-list')) + files.add(os.path.join(base, 'demo/common/assets.js')) closure_opts = build.common_closure_opts + build.common_closure_defines closure_opts += build.debug_closure_opts + build.debug_closure_defines @@ -128,63 +125,31 @@ def check_tests(_): '--jscomp_off=missingRequire', '--jscomp_off=strictMissingRequire', '--checks-only', '-O', 'SIMPLE' ] - return test_build.build_raw(closure_opts) - -def check_externs(_): - """Runs an extra compile pass over the generated externs to ensure that they - are usable. - - Returns: - True on success, False on failure. - """ - logging.info('Checking the usability of generated externs...') - - # Create a complete "build" object. - externs_build = build.Build() - if not externs_build.parse_build(['+@complete'], os.getcwd()): - return False - externs_build.add_core() - - # Use it to generate externs for the next check. - if not externs_build.generate_externs('check'): - return False - - # Create a custom "build" object, add all manually-written externs, then add - # the generated externs we just generated. - source_base = shakaBuildHelpers.get_source_base() - manual_externs = shakaBuildHelpers.get_all_files( - os.path.join(source_base, 'externs'), re.compile(r'.*\.js$')) - generated_externs = os.path.join( - source_base, 'dist', 'shaka-player.check.externs.js') - - check_build = build.Build() - check_build.include = set(manual_externs) - check_build.include.add(generated_externs) - - # Build with the complete set of externs, but without any application code. - # This will help find issues in the generated externs, independent of the app. - # Since we have no app, don't use the defines. Unused defines cause a - # compilation error. - closure_opts = build.common_closure_opts + build.debug_closure_opts + [ - '--checks-only', '-O', 'SIMPLE' - ] - ok = check_build.build_raw(closure_opts) - - # Clean up the temporary externs we just generated. - os.unlink(generated_externs) - - # Return the success/failure of the build above. - return ok + # Set up a build with the build name of "dummy". With output_compiled_bundle + # set to False, the build name is irrelevant, since we won't generate any + # compiled output. + closure = compiler.ClosureCompiler(files, 'dummy') + closure.output_compiled_bundle = False + # Instead of creating a compiled bundle, we will touch a timestamp file to + # keep track of how recently we've run this check. + closure.timestamp_file = os.path.join(base, 'dist', '.testcheckstamp') + return closure.compile(closure_opts, args.force) def main(args): parser = argparse.ArgumentParser( description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) - parser.add_argument('--fix', - help='Automatically fix style violations.', - action='store_true') + parser.add_argument( + '--fix', + help='Automatically fix style violations.', + action='store_true') + parser.add_argument( + '--force', + '-f', + help='Force checks even if no files have changed.', + action='store_true') parsed_args = parser.parse_args(args) @@ -197,7 +162,6 @@ def main(args): check_html_lint, check_complete, check_tests, - check_externs, ] for step in steps: if not step(parsed_args): diff --git a/build/checkversion.py b/build/checkversion.py index 57941686a..5495cd3f2 100755 --- a/build/checkversion.py +++ b/build/checkversion.py @@ -41,7 +41,7 @@ def changelog_version(): return match.group(1) if match else '' -def check_version(_): +def main(_): """Checks that all the versions in the library match.""" changelog = changelog_version() player = player_version() @@ -81,4 +81,4 @@ def check_version(_): if __name__ == '__main__': - shakaBuildHelpers.run_main(check_version) + shakaBuildHelpers.run_main(main) diff --git a/build/compiler.py b/build/compiler.py new file mode 100644 index 000000000..4d0dce220 --- /dev/null +++ b/build/compiler.py @@ -0,0 +1,346 @@ +# Copyright 2016 Google Inc. All Rights Reserved. +# +# 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 +# +# http://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. + +"""Classes representing the various compiler and linter tools that are used to +build Shaka Player.""" + +import json +import logging +import os +import re +import shutil +import subprocess + +import shakaBuildHelpers + + +def _canonicalize_source_files(source_files): + """Canonicalize a set or list of source files. + + This makes all path names cygwin-safe and sorted.""" + + files = [shakaBuildHelpers.cygwin_safe_path(f) for f in source_files] + files.sort() + return files + +def _get_source_path(path): + """Take path components of a source file as arguments and return an absolute, + cygwin-safe path.""" + return shakaBuildHelpers.cygwin_safe_path(os.path.join( + shakaBuildHelpers.get_source_base(), path)) + +def _must_build(output, source_files): + """Returns True if any of the |source_files| have changed since |output| was + built, or if |output| does not exist yet.""" + if not os.path.isfile(output): + # Nothing built, so we should build the output. + return True + + # Detect changes to the set of files that we intend to build. + build_time = os.path.getmtime(output) + # See if any files were modified since the output was created. + if any(os.path.getmtime(f) > build_time for f in source_files): + # Some input files have changed, so we should build again. + return True + + logging.warning('No changes detected, skipping. Use --force to override.') + return False + +def _update_timestamp(path): + # This creates the file if it does not exist, and updates the timestamp if it + # does. + open(path, 'w').close() + + +class ClosureCompiler(object): + def __init__(self, source_files, build_name): + self.source_files = _canonicalize_source_files(source_files) + + prefix = _get_source_path('dist/' + build_name) + self.compiled_js_path = prefix + '.js' + self.source_map_path = prefix + '.map' + + # These can be overridden for special cases: + + # If True, output the compiled bundle to a file. + self.output_compiled_bundle = True + # If True, generate a source map and attach it to the output bundle. + self.add_source_map = True + # If True, wrap the output in a wrapper that prevents window pollution. + self.add_wrapper = True + # If not None, use a timestamp file for change detection, and touch that + # timestamp file after compilation. + self.timestamp_file = None + + def compile(self, options, force=False): + """Builds the files in |self.source_files| using the given Closure + command-line options. + + Args: + options: An array of options to give to Closure. + force: Generate the output even if the inputs have not changed. + + Returns: + True on success; False on failure. + """ + if not force: + if self.timestamp_file: + if not _must_build(self.timestamp_file, self.source_files): + return True + else: + if not _must_build(self.compiled_js_path, self.source_files): + return True + + jar = _get_source_path('third_party/closure/compiler.jar') + + output_options = [] + if self.output_compiled_bundle: + output_options += [ + '--js_output_file', self.compiled_js_path, + ] + + if self.add_source_map: + source_base = _get_source_path('') + + # The source map below would be silently broken if the format of + # source_base changed without updating the source_map_location_mapping + # argument below. This assertion ensures that this doesn't happen. + assert source_base[-1] == '/', 'Source base format changed!' + + output_options += [ + '--create_source_map', self.source_map_path, + '--source_map_location_mapping', source_base + '|../', + ] + + if self.add_wrapper: + output_options += self._prepare_wrapper() + + cmd_line = ['java', '-jar', jar] + output_options + options + cmd_line += self.source_files + + if shakaBuildHelpers.execute_get_code(cmd_line) != 0: + logging.error('Build failed') + return False + + if self.output_compiled_bundle and self.add_source_map: + # Add a special source-mapping comment so that Chrome and Firefox can map + # line and character numbers from the compiled library back to the + # original source locations. + with open(self.compiled_js_path, 'a') as f: + f.write('//# sourceMappingURL=%s' % os.path.basename( + self.source_map_path)) + + if self.timestamp_file: + _update_timestamp(self.timestamp_file) + + return True + + def _prepare_wrapper(self): + """Prepares an output wrapper and returns a list of command line arguments + for Closure Compiler to use it.""" + + # Load the wrapper and use Closure to strip whitespace and comments. + # This requires %output% in the template to be protected, so Closure doesn't + # fail to parse it. + wrapper_input_path = _get_source_path('build/wrapper.template.js') + wrapper_output_path = _get_source_path('dist/wrapper.js') + + with open(wrapper_input_path, 'rb') as f: + wrapper_code = f.read().decode('utf8').replace('%output%', '"%output%"') + + jar = _get_source_path('third_party/closure/compiler.jar') + cmd_line = ['java', '-jar', jar, '-O', 'WHITESPACE_ONLY'] + + proc = shakaBuildHelpers.execute_subprocess( + cmd_line, stdin=subprocess.PIPE, stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + stripped_wrapper_code = proc.communicate(wrapper_code.encode('utf8'))[0] + + if proc.returncode != 0: + raise RuntimeError('Failed to strip whitespace from wrapper!') + + with open(wrapper_output_path, 'wb') as f: + code = stripped_wrapper_code.decode('utf8') + f.write(code.replace('"%output%"', '%output%').encode('utf8')) + + return ['--output_wrapper_file=%s' % wrapper_output_path] + + +class ExternGenerator(object): + def __init__(self, source_files, build_name): + self.source_files = _canonicalize_source_files(source_files) + self.output = _get_source_path('dist/' + build_name + '.externs.js') + + def generate(self, force=False): + """Generates externs for the files in |self.source_files|. + + Args: + force: Generate the output even if the inputs have not changed. + + Returns: + True on success; False on failure. + """ + if not force and not _must_build(self.output, self.source_files): + return True + + extern_generator = _get_source_path('build/generateExterns.js') + + cmd_line = ['node', extern_generator, '--output', self.output] + cmd_line += self.source_files + + if shakaBuildHelpers.execute_get_code(cmd_line) != 0: + logging.error('Externs generation failed') + return False + + return True + + +class Less(object): + def __init__(self, source_files, output): + self.source_files = _canonicalize_source_files(source_files) + self.output = output + + def compile(self, force=False): + """Compiles the less files in |self.source_files| into the |self.output| + css file. + + Args: + force: Generate the output even if the inputs have not changed. + + Returns: + True on success; False on failure. + """ + if not force and not _must_build(self.output, self.source_files): + return True + + lessc = shakaBuildHelpers.get_node_binary('less', 'lessc') + + cmd_line = lessc + self.source_files + [self.output] + + if shakaBuildHelpers.execute_get_code(cmd_line) != 0: + logging.error('Externs generation failed') + return False + + return True + + +class Linter(object): + def __init__(self, source_files, config_path): + self.source_files = _canonicalize_source_files(source_files) + self.config_path = config_path + self.output = _get_source_path('dist/.lintstamp') + + def lint(self, fix=False, force=False): + """Run linter checks on the files in |self.source_files|. + + Args: + fix: If True, ask the linter to fix what errors it can automatically. + force: Run linter checks even if the inputs have not changed. + + Returns: + True on success; False on failure. + """ + deps = self.source_files + [self.config_path] + if not force and not _must_build(self.output, deps): + return True + + eslint = shakaBuildHelpers.get_node_binary('eslint') + cmd_line = eslint + ['--config', self.config_path] + self.source_files + + if fix: + cmd_line += ['--fix'] + + if shakaBuildHelpers.execute_get_code(cmd_line) != 0: + return False + + # TODO: Add back Closure Compiler Linter + + # Update the timestamp of the file that tracks when we last updated. + _update_timestamp(self.output) + return True + + +class HtmlLinter(object): + def __init__(self, source_files, config_path): + self.source_files = _canonicalize_source_files(source_files) + self.config_path = config_path + self.output = _get_source_path('dist/.htmllintstamp') + + def lint(self, force=False): + """Run HTML linter checks on the files in |self.source_files|. + + Args: + force: Run linter checks even if the inputs have not changed. + + Returns: + True on success; False on failure. + """ + deps = self.source_files + [self.config_path] + if not force and not _must_build(self.output, deps): + return True + + htmlhint = shakaBuildHelpers.get_node_binary('htmlhint') + cmd_line = htmlhint + ['--config=' + self.config_path] + self.source_files + + if shakaBuildHelpers.execute_get_code(cmd_line) != 0: + return False + + # Update the timestamp of the file that tracks when we last updated. + _update_timestamp(self.output) + return True + + +class Jsdoc(object): + def __init__(self, config_path): + self.config_path = config_path + self.source_files = [] + + # Just one of many output files, used to check the freshness of the docs. + self.output = _get_source_path('docs/api/index.html') + + # To avoid getting out of sync with the source files jsdoc actually reads, + # parse the config file and locate all source files based on that. + match = re.compile(r'.*\.js$') + with open(self.config_path, 'rb') as f: + config = json.load(f) + for path in config['source']['include']: + full_path = _get_source_path(path) + self.source_files += shakaBuildHelpers.get_all_files(full_path, match) + + def build(self, force=False): + """Build the documentation. + + Args: + force: Build the docs even if the inputs have not changed. + + Returns: + True on success; False on failure. + """ + deps = self.source_files + [self.config_path] + if not force and not _must_build(self.output, deps): + return True + + base = _get_source_path('') + + # Wipe out any old docs. + shutil.rmtree(os.path.join(base, 'docs', 'api'), ignore_errors=True) + + # Jsdoc expects to run from the base dir. + with shakaBuildHelpers.InDir(base): + jsdoc = shakaBuildHelpers.get_node_binary('jsdoc') + cmd_line = jsdoc + ['-c', self.config_path] + if shakaBuildHelpers.execute_get_code(cmd_line) != 0: + return False + + return True diff --git a/build/conformance.textproto b/build/conformance.textproto index f1c9d34f1..3efe940cd 100644 --- a/build/conformance.textproto +++ b/build/conformance.textproto @@ -172,20 +172,29 @@ requirement: { } -# Disallow eval, except when testing for ES6 syntax in demo +# Disallow eval, except when testing for modern JS syntax in demo requirement: { type: BANNED_NAME value: 'window.eval' error_message: 'Using "eval" is not allowed' - whitelist_regexp: 'demo/load.js' - whitelist_regexp: 'demo/main.js' + whitelist_regexp: 'demo/common/demo_utils.js' } requirement: { type: BANNED_NAME value: 'eval' error_message: 'Using "eval" is not allowed' - whitelist_regexp: 'demo/load.js' - whitelist_regexp: 'demo/main.js' + whitelist_regexp: 'demo/common/demo_utils.js' +} + + +# Disallow the Event constructor, since Event is not constructable on IE. +requirement: { + type: BANNED_CODE_PATTERN + value: '/** @param {*} name */ ' + 'function template(name) { new Event(name); }' + error_message: 'Event is not constructable on IE. Instead, use ' + 'document.createEvent(\'CustomEvent\') to create an event and ' + 'event.initCustomEvent(\'my-event-type\') to initialize it.' } diff --git a/build/docs.py b/build/docs.py index cc1808c3a..0688b9984 100755 --- a/build/docs.py +++ b/build/docs.py @@ -19,26 +19,42 @@ This deletes the old documentation first. """ +import argparse import logging import os -import shutil -import sys +import compiler import shakaBuildHelpers -def build_docs(_): +def main(args): """Builds the source code documentation.""" logging.info('Building the docs...') - base = shakaBuildHelpers.get_source_base() - shutil.rmtree(os.path.join(base, 'docs', 'api'), ignore_errors=True) - os.chdir(base) + parser = argparse.ArgumentParser( + description=__doc__, + formatter_class=argparse.RawDescriptionHelpFormatter) + parser.add_argument( + '--force', + '-f', + help='Force the docs to be built, even if no files have changed.', + action='store_true') - jsdoc = shakaBuildHelpers.get_node_binary('jsdoc') - cmd_line = jsdoc + ['-c', 'docs/jsdoc.conf.json'] - return shakaBuildHelpers.execute_get_code(cmd_line) + parsed_args = parser.parse_args(args) + + base = shakaBuildHelpers.get_source_base() + config_path = os.path.join(base, 'docs', 'jsdoc.conf.json') + jsdoc = compiler.Jsdoc(config_path) + if not jsdoc.build(parsed_args.force): + return 1 + return 0 + + +# TODO: Remove this alias. It is here for backward compatibility until private +# scripts depending on it can be updated. +def build_docs(args): + return main(args) if __name__ == '__main__': - shakaBuildHelpers.run_main(build_docs) + shakaBuildHelpers.run_main(main) diff --git a/build/gendeps.py b/build/gendeps.py index 3ade69eb2..15efff419 100755 --- a/build/gendeps.py +++ b/build/gendeps.py @@ -26,11 +26,14 @@ import shakaBuildHelpers deps_args = [ '--root_with_prefix=lib ../../../lib', - '--root_with_prefix=third_party/closure ../../../third_party/closure' + '--root_with_prefix=ui ../../../ui', + '--root_with_prefix=third_party/closure ../../../third_party/closure', + '--root_with_prefix=third_party/language-mapping-list ' + + '../../../third_party/language-mapping-list', ] -def gen_deps(_): +def main(_): """Generates the uncompiled dependencies files.""" logging.info('Generating Closure dependencies...') @@ -54,4 +57,4 @@ def gen_deps(_): if __name__ == '__main__': - shakaBuildHelpers.run_main(gen_deps) + shakaBuildHelpers.run_main(main) diff --git a/build/generate-locales.py b/build/generate-locales.py new file mode 100755 index 000000000..c1ba54ca2 --- /dev/null +++ b/build/generate-locales.py @@ -0,0 +1,342 @@ +#!/usr/bin/env python +# +# Copyright 2018 Google Inc. All Rights Reserved. +# +# 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 +# +# http://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. + +"""Generates Javascript to load localization data. + +To generate code that will load your localization data into the localization +system you need to have your localization data in JSON format that follows the +format: + { + "aliases": { + "BEST_WISHES": "ax32f", + "MAY_YOUR_FORGE_BURN_BRIGHT": "by984", + ... + }, + "localizations": { + "elfish-woodland": { + "ax32f": "Merin sa haryalye alasse", + ... + }, + "dwarfish-north": { + "by984": "tan menu selek lanun khun", + ... + }, + ... + } + } + +For "aliases": + - The key is the alias used to associate the use-case with the entry it + should use. + - The value is the translation id used to identify the text in the locale. + +For "localizations": + - The key is the locale code. + - The value is a map of localization id to localized text. + +For all values in "localizations": + - The key should match a value in "aliases". + - The value should be the localized text. + +An input and an output are needed. If files are not provided for input and/or +output, std-in and std-out will be used. + +Examples: + Read from std-in and write to std-out: + generate-locales.py + Read from input file and write to std: + generate-locales.py --source my-localizations.json + Read from input file and write to output file: + generate-locales.py --source my-localizations.json --output my_localizations.js +""" + +from __future__ import print_function + +import argparse +import codecs +import contextlib +import json +import string +import sys + + +_TAB_CHARACTER = ' ' + + +def UsesConstantSyntax(value): + """Check if |value| follows our style for JavaScript constants.""" + allowed_characters = set(string.ascii_uppercase + string.digits + '_') + + # This uses set difference to find any characters in |value| that don't appear + # in |allowed_characters|. Then it uses boolean logic to return True for an + # empty set. + return not set(value) - allowed_characters + + +def VerifyInputData(data): + """Verifies all the localizations and IDs line-up. + + Returns: + A list of (warning_id, args) where warning_id is a string identifier and + args is a list of arguments for the warning. + """ + alias_mapping = data['aliases'] + localizations = data['localizations'] + + # Look for all the ids that are used across all locales. We will needs this + # to ensure that we have at least one alias defined for each one. + all_translation_ids = set() + for entries in localizations.values(): + all_translation_ids.update(entries.keys()) + + # Get the human readable aliases for each id. + aliases = set(alias_mapping.keys()) + + warnings = [] + + # Check if the readable name for each id is JS-Constant compatible. + for alias in aliases: + if not UsesConstantSyntax(alias): + warnings.append(('bad-alias', (alias,))) + + # Make sure that each translation id has an alias defined. + aliases_ids = set(alias_mapping.values()) + for translation_id in all_translation_ids: + if translation_id not in aliases_ids: + warnings.append(('missing-alias', (translation_id,))) + + # Check if any locales are missing entries found in other locales. + for locale, entries in localizations.items(): + for translation_id in all_translation_ids: + if translation_id not in entries: + warnings.append(('missing-localization', (locale, translation_id))) + + return warnings + + +class Doc(object): + """A string builder class used to build out a tab-sensitive document.""" + + def __init__(self): + # All the lines that make-up this document. + self._lines = [] + + # Track each tab we need to insert ahead of the next line. + self._tab_level = 0 + + @contextlib.contextmanager + def Block(self): + """Starts a new tabbed block. + + This should be used with |with| to ensure that the block closes. + """ + self._tab_level += 1 + yield + self._tab_level -= 1 + + def Code(self, block): + """Insert a block of code with the current tab level. + + This will add the required leading white space to the line. + """ + # Break the code block into each line of code + lines = block.split('\n') + + for line in lines: + # Right-strip the line to avoid trailing white space. We do this on the + # full string so that tabbing will be removed if a blank line was added. + new_line = (_TAB_CHARACTER * self._tab_level) + line + self._lines.append(new_line.rstrip()) + + def __str__(self): + return '\n'.join(self._lines) + + +def AsQuotedString(input_string): + """Convert |input_string| into a quoted string.""" + subs = [ + ('\n', '\\n'), + ('\t', '\\t'), + ("'", "\\'") + ] + + # Go through each substitution and replace any occurrences. + output_string = input_string + for before, after in subs: + output_string = output_string.replace(before, after) + + # Lastly wrap the string in quotes. + return "'%s'" % output_string + + +def GenerateLocales(alias_mapping, localizations, class_name): + """Generates JavaScript code to insert the localization data. + + This creates a function called "apply" in the class called |class_name| that, + when called, will insert the data from |localizations|. + + Args: + id_mappings: A map of string tag to a string JavaScript constant name. + localizations: A map of string locale name to a map of string tag to the + string localization. + class_name: A string name of the class to put generated code into. + + Returns: + A string containing the generated code. + """ + doc = Doc() + + doc.Code("""/** + * @license + * Copyright 2016 Google Inc. + * + * 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 + * + * http://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. + */ +""") + + doc.Code(""" +// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +// This file is auto-generated. DO NOT EDIT THIS FILE. If you need to: +// - change which locales are in this file, update "build/locales.json" +// - change an entry for a specific locale, update "build/locales.json" +// - change anything else, update "build/generate-locales.py". +// +// To regenerate this file, run "build/generate-locales.py". +// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +""") + + doc.Code("goog.provide('%s');" % class_name) + doc.Code("goog.provide('%s.Ids');" % class_name) + doc.Code("goog.require('shaka.ui.Localization');") + + doc.Code(""" +/** + * Insert all localization data for the UI into |localization|. This should be + * done BEFORE any listeners are added to the localization system (to avoid + * callbacks for each insert) and should be done BEFORE changing to the initial + * preferred locale (reduces the work needed to update the internal state after + * each insert). + * + * @param {!shaka.ui.Localization} localization + */ +""") + + doc.Code('%s.apply = function(localization) {' % class_name) + + # Go through the locales in sorted order so that we will be consistent between + # runs. + for locale in sorted(localizations.keys()): + localization = localizations[locale] + + with doc.Block(): + quoted_locale = AsQuotedString(locale) + doc.Code('localization.insert(%s, new Map([' % quoted_locale) + + with doc.Block(): + # Make sure that we sort by the localization keys so that they will + # always be in the same order. + for key, value in sorted(localization.items()): + quoted_key = AsQuotedString(key) + quoted_value = AsQuotedString(value) + doc.Code('[%s, %s],' % (quoted_key, quoted_value)) + + doc.Code(']));') # Close the call to insert. + + doc.Code('};') # Close the function. + + # Convert the map to an array with the key and value reversed so + # that we can sort them based on the alias. + constants = [] + for alias, translation_id in alias_mapping.items(): + constants.append((alias, translation_id)) + constants.sort() + + for alias, translation_id in constants: + doc.Code('') # Make sure we have a blank line before each constant. + doc.Code('/** @const {string} */') + doc.Code('%s.Ids.%s = %s;' % (class_name, + alias, + AsQuotedString(translation_id))) + + doc.Code('') # Need blank line at the end of the file + + return doc + + +def CreateParser(): + """Create the argument parser for this application.""" + parser = argparse.ArgumentParser( + description=__doc__, + formatter_class=argparse.RawDescriptionHelpFormatter) + + parser.add_argument( + '--source', + dest='source', + type=str, + help='The file path for JSON input. (default: std in).') + + parser.add_argument( + '--output', + dest='output', + type=str, + help='The file path for JavaScript output (default: std out).') + + parser.add_argument( + '--class-name', + dest='class_name', + type=str, + help='The fully qualified class name for the JavaScript output', + default='shaka.ui.Locales') + + return parser + + +def main(args): + parser = CreateParser() + args = parser.parse_args(args) + + if args.source: + with open(args.source, 'r') as f: + blob = json.load(f) + else: + if sys.stdin.isatty(): + sys.stderr.write('Reading input JSON from stdin...\n') + blob = json.load(sys.stdin) + + for warning_id, warning_args in VerifyInputData(blob): + sys.stderr.write('WARNING: %s %s\n' % (warning_id, warning_args)) + + doc = GenerateLocales(blob['aliases'], blob['localizations'], args.class_name) + + if args.output: + with codecs.open(args.output, 'w', 'utf-8') as f: + f.write(unicode(doc)) + else: + sys.stdout.write(unicode(doc)) + + +if __name__ == '__main__': + main(sys.argv[1:]) diff --git a/build/generateExterns.js b/build/generateExterns.js index d5109aafe..b2580f90d 100755 --- a/build/generateExterns.js +++ b/build/generateExterns.js @@ -647,8 +647,13 @@ function main(args) { for (var i = 1; i < pieces.length; ++i) { var partialName = pieces.slice(0, i).join('.'); if (!namespaces.has(partialName)) { - var declaration = '/** @const */\n'; - if (i == 1) declaration += 'var '; + var declaration; + if (i == 1) { + declaration = '/** @namespace */\n'; + declaration += 'window.'; + } else { + declaration = '/** @const */\n'; + } declaration += partialName + ' = {};\n'; namespaceDeclarations.push(declaration); namespaces.add(partialName); @@ -664,6 +669,8 @@ function main(args) { '/**\n' + ' * @fileoverview Generated externs. DO NOT EDIT!\n' + ' * @externs\n' + + ' * @suppress {duplicate} To prevent compiler errors with the namespace\n' + + ' * being declared both here and by goog.provide in the library.\n' + ' */\n\n' + namespaceDeclarations.join('') + '\n' + externs); } diff --git a/build/locales.json b/build/locales.json new file mode 100644 index 000000000..d1581e731 --- /dev/null +++ b/build/locales.json @@ -0,0 +1,485 @@ +{ + "aliases":{ + "ARIA_LABEL_BACK":"1077325112364709655", + "ARIA_LABEL_CAPTIONS":"1911090580951495029", + "ARIA_LABEL_CAST":"7071612439610534706", + "ARIA_LABEL_EXIT_FULL_SCREEN":"6161306839322897077", + "ARIA_LABEL_FAST_FORWARD":"1774834209035716827", + "ARIA_LABEL_FULL_SCREEN":"8345190086337560158", + "ARIA_LABEL_LANGUAGE":"3278592358864783064", + "ARIA_LABEL_LIVE":"3045980486001972586", + "ARIA_LABEL_MORE_SETTINGS":"4388316720828367903", + "ARIA_LABEL_MUTE":"5963689277976480680", + "ARIA_LABEL_PAUSE":"9042260521669277115", + "ARIA_LABEL_PLAY":"836055097473758014", + "ARIA_LABEL_RESOLUTION":"6073266792045231479", + "ARIA_LABEL_REWIND":"1142734805932039923", + "ARIA_LABEL_SEEK":"5553522235935533682", + "ARIA_LABEL_UNMUTE":"2023925063728908356", + "ARIA_LABEL_VOLUME":"1050953507607739202", + "LABEL_AUTO_QUALITY":"4259064532355692191", + "LABEL_CAPTIONS":"1911090580951495029", + "LABEL_CAPTIONS_OFF":"8145129506114534451", + "LABEL_CAST":"7071612439610534706", + "LABEL_LANGUAGE":"3278592358864783064", + "LABEL_LIVE":"3045980486001972586", + "LABEL_MULTIPLE_LANGUAGES":"411375375680850814", + "LABEL_NOT_APPLICABLE":"411375375680850814", + "LABEL_NOT_CASTING":"8145129506114534451", + "LABEL_RESOLUTION":"6073266792045231479", + "LABEL_UNKNOWN_LANGUAGE":"298626259350585300" + }, + "localizations":{ + "ar":{ + "1077325112364709655":"رجوع", + "8345190086337560158":"ملء الشاشة", + "6161306839322897077":"إنهاء وضع ملء الشاشة", + "1774834209035716827":"تقديم سريع", + "3045980486001972586":"مباشر", + "5963689277976480680":"كتم الصوت", + "1911090580951495029":"الترجمة", + "9042260521669277115":"إيقاف مؤقت", + "836055097473758014":"تشغيل", + "1142734805932039923":"إرجاع", + "4388316720828367903":"إعدادات إضافية", + "5553522235935533682":"شريط تمرير البحث", + "7071612439610534706":"إرسال...", + "2023925063728908356":"إلغاء كتم الصوت", + "1050953507607739202":"الحجم", + "4259064532355692191":"تلقائي", + "8145129506114534451":"إيقاف", + "3278592358864783064":"اللغة", + "298626259350585300":"غير معروفة" + }, + "de":{ + "1077325112364709655":"Zurück", + "8345190086337560158":"Vollbild", + "6161306839322897077":"Vollbildmodus beenden", + "1774834209035716827":"Vorspulen", + "3045980486001972586":"Live", + "5963689277976480680":"Stummschalten", + "1911090580951495029":"Untertitel", + "9042260521669277115":"Pausieren", + "836055097473758014":"Wiedergeben", + "1142734805932039923":"Zurückspulen", + "4388316720828367903":"Weitere Einstellungen", + "6073266792045231479":"Auflösung", + "5553522235935533682":"Schieberegler für Suche", + "7071612439610534706":"Streamen…", + "2023925063728908356":"Stummschaltung aufheben", + "1050953507607739202":"Lautstärke", + "4259064532355692191":"Automatisch", + "8145129506114534451":"Aus", + "3278592358864783064":"Sprache", + "411375375680850814":"Nicht zutreffend", + "298626259350585300":"Unbekannt" + }, + "en-GB":{ + "1077325112364709655":"Back", + "8345190086337560158":"Full screen", + "6161306839322897077":"Exit full screen", + "1774834209035716827":"Fast-forward", + "3045980486001972586":"Live", + "5963689277976480680":"Mute", + "1911090580951495029":"Captions", + "9042260521669277115":"Pause", + "836055097473758014":"Play", + "1142734805932039923":"Rewind", + "4388316720828367903":"More settings", + "6073266792045231479":"Resolution", + "5553522235935533682":"Seek slider", + "7071612439610534706":"Cast...", + "2023925063728908356":"Unmute", + "1050953507607739202":"volume", + "4259064532355692191":"Auto", + "8145129506114534451":"Off", + "3278592358864783064":"Language", + "298626259350585300":"Unknown" + }, + "en":{ + "1077325112364709655":"Back", + "8345190086337560158":"Full screen", + "6161306839322897077":"Exit full screen", + "1774834209035716827":"Fast-forward", + "3045980486001972586":"Live", + "5963689277976480680":"Mute", + "1911090580951495029":"Captions", + "9042260521669277115":"Pause", + "836055097473758014":"Play", + "1142734805932039923":"Rewind", + "4388316720828367903":"More settings", + "6073266792045231479":"Resolution", + "5553522235935533682":"Seek slider", + "7071612439610534706":"Cast...", + "2023925063728908356":"Unmute", + "1050953507607739202":"volume", + "4259064532355692191":"Auto", + "8145129506114534451":"Off", + "3278592358864783064":"Language", + "411375375680850814":"N/A", + "298626259350585300":"Unknown" + }, + "es":{ + "1077325112364709655":"Atrás", + "8345190086337560158":"Pantalla completa", + "6161306839322897077":"Salir del modo de pantalla completa", + "1774834209035716827":"Avance rápido", + "3045980486001972586":"En directo", + "5963689277976480680":"Silenciar", + "1911090580951495029":"Subtítulos", + "9042260521669277115":"Pausa", + "836055097473758014":"Reproducir", + "1142734805932039923":"Retroceder", + "4388316720828367903":"Más ajustes", + "6073266792045231479":"Resolución", + "5553522235935533682":"Barra deslizante de búsqueda", + "7071612439610534706":"Reparto...", + "2023925063728908356":"Activar sonido", + "1050953507607739202":"volumen", + "4259064532355692191":"Automática", + "8145129506114534451":"No", + "3278592358864783064":"Idioma", + "411375375680850814":"No aplicable", + "298626259350585300":"Desconocido" + }, + "es-419":{ + "1077325112364709655":"Atrás", + "8345190086337560158":"Pantalla completa", + "6161306839322897077":"Salir de pantalla completa", + "1774834209035716827":"Avance rápido", + "3045980486001972586":"En vivo", + "5963689277976480680":"Silenciar", + "1911090580951495029":"Subtítulos", + "9042260521669277115":"Detener", + "836055097473758014":"Jugar", + "1142734805932039923":"Retroceder", + "4388316720828367903":"Más opciones de configuración", + "6073266792045231479":"Resolución", + "5553522235935533682":"Barra deslizante de búsqueda", + "7071612439610534706":"Transmitir…", + "2023925063728908356":"Activar sonido", + "1050953507607739202":"volumen", + "4259064532355692191":"Auto", + "8145129506114534451":"Desactivado", + "3278592358864783064":"Idioma", + "298626259350585300":"Desconocido" + }, + "fr":{ + "1077325112364709655":"Retour", + "8345190086337560158":"Plein écran", + "6161306839322897077":"Quitter le mode plein écran", + "1774834209035716827":"Avance rapide", + "3045980486001972586":"En direct", + "5963689277976480680":"Désactiver le son", + "1911090580951495029":"Sous-titres", + "9042260521669277115":"Mettre en veille", + "836055097473758014":"Lire", + "1142734805932039923":"Retour arrière", + "4388316720828367903":"Autres paramètres", + "6073266792045231479":"Résolution", + "5553522235935533682":"Barre de recherche", + "7071612439610534706":"Caster sur…", + "2023925063728908356":"Activer le son", + "1050953507607739202":"volume", + "4259064532355692191":"Auto", + "8145129506114534451":"Désactivée", + "3278592358864783064":"Langue", + "411375375680850814":"Sans objet", + "298626259350585300":"Inconnue" + }, + "it":{ + "1077325112364709655":"Indietro", + "8345190086337560158":"Schermo intero", + "6161306839322897077":"Esci dalla modalità a schermo intero", + "1774834209035716827":"Avanti veloce", + "3045980486001972586":"Dal vivo", + "5963689277976480680":"Disattiva audio", + "1911090580951495029":"Sottotitoli", + "9042260521669277115":"Metti in pausa", + "836055097473758014":"Riproduci", + "1142734805932039923":"Riavvolgi", + "4388316720828367903":"Altre impostazioni", + "6073266792045231479":"Risoluzione", + "5553522235935533682":"Dispositivo di scorrimento", + "7071612439610534706":"Trasmetti…", + "2023925063728908356":"Riattiva audio", + "1050953507607739202":"volume", + "4259064532355692191":"Auto", + "8145129506114534451":"Disattivato", + "3278592358864783064":"Lingua", + "411375375680850814":"N/A", + "298626259350585300":"Sconosciuto" + }, + "ja":{ + "1077325112364709655":"戻る", + "8345190086337560158":"全画面", + "6161306839322897077":"全画面モードの終了", + "1774834209035716827":"早送り", + "3045980486001972586":"ライブ", + "5963689277976480680":"ミュート", + "1911090580951495029":"字幕", + "9042260521669277115":"一時停止", + "836055097473758014":"再生", + "1142734805932039923":"巻き戻し", + "4388316720828367903":"その他の設定", + "6073266792045231479":"解像度", + "5553522235935533682":"シーク バー", + "7071612439610534706":"キャスト...", + "2023925063728908356":"ミュート解除", + "1050953507607739202":"音量", + "4259064532355692191":"自動", + "8145129506114534451":"オフ", + "3278592358864783064":"言語", + "411375375680850814":"該当なし", + "298626259350585300":"不明" + }, + "ko":{ + "1077325112364709655":"뒤로", + "8345190086337560158":"전체화면", + "6161306839322897077":"전체화면 종료", + "1774834209035716827":"빨리감기", + "3045980486001972586":"라이브", + "5963689277976480680":"음소거", + "1911090580951495029":"자막", + "9042260521669277115":"일시중지", + "836055097473758014":"재생", + "1142734805932039923":"되감기", + "4388316720828367903":"설정 더보기", + "6073266792045231479":"해상도", + "5553522235935533682":"탐색 슬라이더", + "7071612439610534706":"전송...", + "2023925063728908356":"음소거 해제", + "1050953507607739202":"볼륨", + "4259064532355692191":"자동", + "8145129506114534451":"사용 안함", + "3278592358864783064":"언어", + "411375375680850814":"해당 없음", + "298626259350585300":"알 수 없음" + }, + "nl":{ + "1077325112364709655":"Terug", + "8345190086337560158":"Volledig scherm", + "6161306839322897077":"Volledig scherm afsluiten", + "1774834209035716827":"Vooruitspoelen", + "3045980486001972586":"Live", + "5963689277976480680":"Dempen", + "1911090580951495029":"Ondertiteling", + "9042260521669277115":"Onderbreken", + "836055097473758014":"Afspelen", + "1142734805932039923":"Terugspoelen", + "4388316720828367903":"Meer instellingen", + "6073266792045231479":"Resolutie", + "5553522235935533682":"Zoekschuifbalk", + "7071612439610534706":"Casten...", + "2023925063728908356":"Dempen opheffen", + "1050953507607739202":"volume", + "4259064532355692191":"Automatisch", + "8145129506114534451":"Uit", + "3278592358864783064":"Taal", + "411375375680850814":"N.v.t.", + "298626259350585300":"Onbekend" + }, + "pl":{ + "1077325112364709655":"Wstecz", + "8345190086337560158":"Pełny ekran", + "6161306839322897077":"Zamknij pełny ekran", + "1774834209035716827":"Przewiń do przodu", + "3045980486001972586":"Na żywo", + "5963689277976480680":"Wycisz", + "1911090580951495029":"Napisy", + "9042260521669277115":"Wstrzymaj", + "836055097473758014":"Odtwarzaj", + "1142734805932039923":"Przewiń do tyłu", + "4388316720828367903":"Więcej ustawień", + "6073266792045231479":"Rozdzielczość", + "5553522235935533682":"Suwak przewijania", + "7071612439610534706":"Prześlij...", + "2023925063728908356":"Wyłącz wyciszenie", + "1050953507607739202":"głośność", + "4259064532355692191":"Automatyczna", + "8145129506114534451":"Wyłączone", + "3278592358864783064":"Język", + "411375375680850814":"N/d", + "298626259350585300":"Nieznane" + }, + "pt-BR":{ + "1077325112364709655":"Voltar", + "8345190086337560158":"Tela inteira", + "6161306839322897077":"Sair da tela inteira", + "1774834209035716827":"Avançar", + "3045980486001972586":"Ao vivo", + "5963689277976480680":"Desativar som", + "1911090580951495029":"Legendas ocultas", + "9042260521669277115":"Pausar", + "836055097473758014":"Reproduzir", + "1142734805932039923":"Retroceder", + "4388316720828367903":"Mais configurações", + "6073266792045231479":"Resolução", + "5553522235935533682":"Botão deslizante de busca", + "7071612439610534706":"Elenco...", + "2023925063728908356":"Ativar som", + "1050953507607739202":"volume", + "4259064532355692191":"Automático", + "8145129506114534451":"Desativado", + "3278592358864783064":"Idioma", + "411375375680850814":"N/A", + "298626259350585300":"Desconhecido" + }, + "pt-PT":{ + "1077325112364709655":"Anterior", + "8345190086337560158":"Ecrã inteiro", + "6161306839322897077":"Sair do ecrã inteiro", + "1774834209035716827":"Avançar", + "3045980486001972586":"Em direto", + "5963689277976480680":"Desativar o som", + "1911090580951495029":"Legendas", + "9042260521669277115":"Colocar em pausa", + "836055097473758014":"Reproduzir", + "1142734805932039923":"Recuar", + "4388316720828367903":"Mais definições", + "6073266792045231479":"Resolução", + "5553522235935533682":"Controlo de deslize da procura", + "7071612439610534706":"Transmitir...", + "2023925063728908356":"Reativar o som", + "1050953507607739202":"volume", + "4259064532355692191":"Automático", + "8145129506114534451":"Desativado", + "3278592358864783064":"Idioma", + "411375375680850814":"N/A", + "298626259350585300":"Desconhecida" + }, + "ru":{ + "1077325112364709655":"Назад", + "8345190086337560158":"Во весь экран", + "6161306839322897077":"Выход из полноэкранного режима", + "1774834209035716827":"Перемотать вперед", + "3045980486001972586":"В эфире", + "5963689277976480680":"Отключить звук", + "1911090580951495029":"Субтитры", + "9042260521669277115":"Приостановить", + "836055097473758014":"Смотреть", + "1142734805932039923":"Перемотать назад", + "4388316720828367903":"Дополнительные настройки", + "6073266792045231479":"Разрешение", + "5553522235935533682":"Ползунок поиска", + "7071612439610534706":"Добавить", + "2023925063728908356":"Включить звук", + "1050953507607739202":"громкость", + "4259064532355692191":"Автонастройка", + "8145129506114534451":"Выкл.", + "3278592358864783064":"Язык", + "411375375680850814":"–", + "298626259350585300":"Неизвестно" + }, + "th":{ + "1077325112364709655":"กลับ", + "8345190086337560158":"เต็มหน้าจอ", + "6161306839322897077":"ออกจากโหมดเต็มหน้าจอ", + "1774834209035716827":"กรอไปข้างหน้า", + "3045980486001972586":"สด", + "5963689277976480680":"ปิดเสียง", + "1911090580951495029":"คำอธิบายวิดีโอ", + "9042260521669277115":"หยุดชั่วคราว", + "836055097473758014":"เล่น", + "1142734805932039923":"กรอกลับ", + "4388316720828367903":"การตั้งค่าเพิ่มเติม", + "6073266792045231479":"ความละเอียด", + "5553522235935533682":"แถบเลื่อนค้นหา", + "7071612439610534706":"แคสต์...", + "2023925063728908356":"เปิดเสียง", + "1050953507607739202":"ระดับเสียง", + "4259064532355692191":"อัตโนมัติ", + "8145129506114534451":"ปิด", + "3278592358864783064":"ภาษา", + "298626259350585300":"ไม่ทราบ" + }, + "tr":{ + "1077325112364709655":"Geri", + "8345190086337560158":"Tam ekran", + "6161306839322897077":"Tam ekrandan çık", + "1774834209035716827":"İleri sar", + "3045980486001972586":"Canlı", + "5963689277976480680":"Sesi kapat", + "1911090580951495029":"Altyazılar", + "9042260521669277115":"Duraklat", + "836055097473758014":"Oynat", + "1142734805932039923":"Geri sar", + "4388316720828367903":"Diğer ayarlar", + "6073266792045231479":"Çözünürlük", + "5553522235935533682":"Arama kaydırma çubuğu", + "7071612439610534706":"Yayınla...", + "2023925063728908356":"Sesi aç", + "1050953507607739202":"ses düzeyi", + "4259064532355692191":"Otomatik", + "8145129506114534451":"Kapalı", + "3278592358864783064":"Dil", + "298626259350585300":"Bilinmiyor" + }, + "zh-HK":{ + "1077325112364709655":"返回", + "8345190086337560158":"全螢幕", + "6161306839322897077":"結束全螢幕", + "1774834209035716827":"快轉", + "3045980486001972586":"直播", + "5963689277976480680":"靜音", + "1911090580951495029":"字幕", + "9042260521669277115":"暫停", + "836055097473758014":"播放", + "1142734805932039923":"倒帶", + "4388316720828367903":"更多設定", + "5553522235935533682":"搜尋滑桿", + "7071612439610534706":"投放…", + "2023925063728908356":"解除靜音", + "1050953507607739202":"音量", + "4259064532355692191":"自動", + "8145129506114534451":"未選取", + "3278592358864783064":"語言", + "298626259350585300":"不明" + }, + "zh-CN":{ + "1077325112364709655":"返回", + "8345190086337560158":"全屏", + "6161306839322897077":"退出全屏", + "1774834209035716827":"快进", + "3045980486001972586":"直播", + "5963689277976480680":"静音", + "1911090580951495029":"字幕", + "9042260521669277115":"暂停", + "836055097473758014":"播放", + "1142734805932039923":"快退", + "4388316720828367903":"更多设置", + "6073266792045231479":"分辨率", + "5553522235935533682":"播放滑块", + "7071612439610534706":"投射…", + "2023925063728908356":"取消静音", + "1050953507607739202":"音量", + "4259064532355692191":"自动", + "8145129506114534451":"关闭", + "3278592358864783064":"语言", + "298626259350585300":"未知" + }, + "zh-TW":{ + "1077325112364709655":"返回", + "8345190086337560158":"全螢幕", + "6161306839322897077":"結束全螢幕", + "1774834209035716827":"快轉", + "3045980486001972586":"直播", + "5963689277976480680":"靜音", + "1911090580951495029":"字幕", + "9042260521669277115":"暫停", + "836055097473758014":"播放", + "1142734805932039923":"倒轉", + "4388316720828367903":"更多設定", + "6073266792045231479":"解析度", + "5553522235935533682":"搜尋滑桿", + "7071612439610534706":"投放…", + "2023925063728908356":"解除靜音", + "1050953507607739202":"音量", + "4259064532355692191":"自動", + "8145129506114534451":"關閉", + "3278592358864783064":"語言", + "411375375680850814":"不適用", + "298626259350585300":"未知" + } + } +} diff --git a/build/shakaBuildHelpers.py b/build/shakaBuildHelpers.py index 93a5cb6bf..286a85f01 100644 --- a/build/shakaBuildHelpers.py +++ b/build/shakaBuildHelpers.py @@ -61,7 +61,11 @@ def _parse_version(version): def get_source_base(): """Returns the absolute path to the source code base.""" - return os.path.abspath(os.path.join(os.path.dirname(__file__), '..')) + source_base = os.path.abspath(os.path.join(os.path.dirname(__file__), '..')) + # In the build files, we use '/' in the paths, however Windows uses '\'. + # Although Windows supports both, the source mapping will not work with '\'. + # So we use Linux-style paths for everything. + return source_base.replace('\\', '/') def is_linux(): @@ -219,24 +223,35 @@ def get_all_files(dir_path, exp=None): return ret -def get_node_binary(name): +def get_node_binary(module_name, bin_name=None): """Returns an array to be used in the command-line execution of a node binary. For example, this may return ['eslint'] (global install) or ['node', 'path/to/node_modules/eslint/bin/eslint.js'] (local install). + + Arguments: + module_name: A string, the name of the module. + bin_name: An optional string, the name of the binary, which defaults to + module_name if not provided. + + Returns: + An array of strings which form the command-line to call the binary. """ + if not bin_name: + bin_name = module_name + # Check local modules first. base = get_source_base() - path = os.path.join(base, 'node_modules', name) + path = os.path.join(base, 'node_modules', module_name) if os.path.isdir(path): json_path = os.path.join(path, 'package.json') package_data = json.load(open(json_path, 'r')) - bin_path = os.path.join(path, package_data['bin'][name]) + bin_path = os.path.join(path, package_data['bin'][bin_name]) return ['node', bin_path] # Not found locally, assume it can be found in os.environ['PATH']. - return [name] + return [bin_name] class InDir(object): diff --git a/build/stats.py b/build/stats.py index 7959ffb94..c85248553 100755 --- a/build/stats.py +++ b/build/stats.py @@ -43,7 +43,6 @@ import logging import math import os import string -import sys import shakaBuildHelpers diff --git a/build/test.py b/build/test.py index 690ae0b25..aefc2cd17 100755 --- a/build/test.py +++ b/build/test.py @@ -405,7 +405,7 @@ class Launcher: # There is no need to print a status here as the gendep and build # calls will print their own status updates. if self.parsed_args.build: - if gendeps.gen_deps([]) != 0: + if gendeps.main([]) != 0: logging.error('Failed to generate project dependencies') return 1 diff --git a/build/types/complete b/build/types/complete index 9e3ac577b..db81a25f6 100644 --- a/build/types/complete +++ b/build/types/complete @@ -5,3 +5,4 @@ +@manifests +@polyfill +@text ++@ui diff --git a/build/types/ui b/build/types/ui new file mode 100644 index 000000000..23ad4b098 --- /dev/null +++ b/build/types/ui @@ -0,0 +1,8 @@ +# UI library. + ++../../third_party/language-mapping-list/language-mapping-list.js ++../../ui/controls.js ++../../ui/localization.js ++../../ui/locales.js ++../../ui/ui.js ++../../ui/ui_utils.js diff --git a/demo/asset_section.js b/demo/asset_section.js index d3471dd90..b148ac65c 100644 --- a/demo/asset_section.js +++ b/demo/asset_section.js @@ -346,6 +346,12 @@ shakaDemo.load = function() { // Update the control state in case autoplay is disabled. shakaDemo.controls_.loadComplete(); + if (shakaDemo.video_.controls) { + shakaDemo.controls_.setEnabledNativeControls(true); + } else { + shakaDemo.controls_.setEnabledShakaControls(true); + } + shakaDemo.hashShouldChange_(); // Set a different poster for audio-only assets. @@ -389,4 +395,7 @@ shakaDemo.load = function() { /** Unload any current asset. */ shakaDemo.unload = function() { shakaDemo.player_.unload(); + if (!shakaDemo.castProxy_.isCasting()) { + shakaDemo.controls_.setEnabledShakaControls(false); + } }; diff --git a/demo/cast_receiver/index.html b/demo/cast_receiver/index.html index 2a6034107..0e7af3848 100644 --- a/demo/cast_receiver/index.html +++ b/demo/cast_receiver/index.html @@ -19,7 +19,7 @@