mirror of
https://github.com/shaka-project/shaka-player.git
synced 2026-06-14 15:56:38 +03:00
Initial release of Shaka Player UI
Other contributors: - @joeyparrish - @michellezhuogg - @TheModMaker - @theodab - @vaage Change-Id: If6df33d9ab5035d1ead4402004f7de37ee8470f4
This commit is contained in:
+38
-19
@@ -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)
|
||||
|
||||
Executable
+170
@@ -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)
|
||||
+28
-266
@@ -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)
|
||||
|
||||
+39
-75
@@ -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):
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
@@ -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.'
|
||||
}
|
||||
|
||||
|
||||
|
||||
+26
-10
@@ -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)
|
||||
|
||||
+6
-3
@@ -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)
|
||||
|
||||
Executable
+342
@@ -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:])
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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":"未知"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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):
|
||||
|
||||
@@ -43,7 +43,6 @@ import logging
|
||||
import math
|
||||
import os
|
||||
import string
|
||||
import sys
|
||||
|
||||
import shakaBuildHelpers
|
||||
|
||||
|
||||
+1
-1
@@ -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
|
||||
|
||||
|
||||
@@ -5,3 +5,4 @@
|
||||
+@manifests
|
||||
+@polyfill
|
||||
+@text
|
||||
+@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
|
||||
@@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Shaka Player Cast Demo</title>
|
||||
<link rel="stylesheet" href="../common/controls.css">
|
||||
<link rel="stylesheet" href="../../ui/controls.css">
|
||||
<link rel="stylesheet" href="receiver_app.css">
|
||||
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto">
|
||||
@@ -29,16 +29,16 @@
|
||||
|
||||
<script>
|
||||
COMPILED_JS = [
|
||||
// The compiled library.
|
||||
'../../dist/shaka-player.compiled.js',
|
||||
// The compiled library, with UI.
|
||||
'../../dist/shaka-player.ui.js',
|
||||
// The compiled receiver app.
|
||||
'../../dist/receiver.compiled.js'
|
||||
'../../dist/receiver.compiled.js',
|
||||
];
|
||||
COMPILED_DEBUG_JS = [
|
||||
// The compiled library, debug mode.
|
||||
'../../dist/shaka-player.compiled.debug.js',
|
||||
// The compiled library, with UI, debug mode.
|
||||
'../../dist/shaka-player.ui.debug.js',
|
||||
// The compiled receiver app.
|
||||
'../../dist/receiver.compiled.debug.js'
|
||||
'../../dist/receiver.compiled.debug.js',
|
||||
];
|
||||
UNCOMPILED_JS = [
|
||||
// Bootstrap the Shaka Player library through the Closure library.
|
||||
@@ -48,9 +48,8 @@ UNCOMPILED_JS = [
|
||||
'../../shaka-player.uncompiled.js',
|
||||
// These are the individual parts of the receiver app.
|
||||
'../common/assets.js',
|
||||
'../common/controls.js',
|
||||
'../common/demo_utils.js',
|
||||
'receiver_app.js'
|
||||
'receiver_app.js',
|
||||
];
|
||||
</script>
|
||||
|
||||
@@ -58,26 +57,14 @@ UNCOMPILED_JS = [
|
||||
<script defer src="../load.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="videoContainer" class="overlay-parent">
|
||||
<video id="video" autoplay></video>
|
||||
<div id="bufferingSpinner" class="overlay">
|
||||
<svg class="spinnerSvg" viewBox="25 25 50 50">
|
||||
<circle class="spinnerPath" cx="50" cy="50" r="20"
|
||||
fill="none" stroke-width="2" stroke-miterlimit="10" />
|
||||
</svg>
|
||||
</div>
|
||||
<div id="controlsContainer" class="overlay"><div id="controls">
|
||||
<button id="pauseIcon" class="material-icons">pause</button>
|
||||
<div id="currentTime">0:00</div>
|
||||
<input id="seekBar" type="range" step="any" min="0" max="1" value="0">
|
||||
</div></div>
|
||||
|
||||
<!-- Covers everything else while idle -->
|
||||
<div id="idle" class="overlay">
|
||||
<h1>Ready to try Shaka's Chromecast support?</h1>
|
||||
<h2>Select an asset in your browser and click "Load".
|
||||
Control the video using the controls in your browser.</h2>
|
||||
</div>
|
||||
<div data-shaka-player-container id="videoContainer">
|
||||
<video autoplay data-shaka-player id="video"></video>
|
||||
</div>
|
||||
<!-- Covers everything else while idle -->
|
||||
<div id="idle" class="overlay">
|
||||
<h1>Ready to try Shaka's Chromecast support?</h1>
|
||||
<h2>Select an asset in your browser and click "Load".
|
||||
Control the video using the controls in your browser.</h2>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -16,6 +16,9 @@
|
||||
*/
|
||||
|
||||
|
||||
goog.require('goog.asserts');
|
||||
|
||||
|
||||
/**
|
||||
* A Chromecast receiver demo app.
|
||||
* @constructor
|
||||
@@ -31,15 +34,12 @@ function ShakaReceiver() {
|
||||
/** @private {shaka.cast.CastReceiver} */
|
||||
this.receiver_ = null;
|
||||
|
||||
/** @private {Element} */
|
||||
this.pauseIcon_ = null;
|
||||
/** @private {HTMLElement} */
|
||||
this.playButton_ = null;
|
||||
|
||||
/** @private {Element} */
|
||||
/** @private {HTMLElement} */
|
||||
this.controlsElement_ = null;
|
||||
|
||||
/** @private {ShakaControls} */
|
||||
this.controlsUi_ = null;
|
||||
|
||||
/** @private {?number} */
|
||||
this.controlsTimerId_ = null;
|
||||
|
||||
@@ -62,17 +62,30 @@ function ShakaReceiver() {
|
||||
* Initialize the application.
|
||||
*/
|
||||
ShakaReceiver.prototype.init = function() {
|
||||
// TODO: Check if this is needed after fixing IE
|
||||
shaka.polyfill.installAll();
|
||||
|
||||
this.video_ =
|
||||
/** @type {!HTMLMediaElement} */(document.getElementById('video'));
|
||||
this.player_ = new shaka.Player(this.video_);
|
||||
/** @type {HTMLMediaElement} */
|
||||
let video = /** @type {HTMLMediaElement} */
|
||||
(document.getElementById('video'));
|
||||
goog.asserts.assert(video, 'Video element should be available!');
|
||||
this.video_ = video;
|
||||
|
||||
this.controlsUi_ = new ShakaControls();
|
||||
this.controlsUi_.initMinimal(this.video_, this.player_);
|
||||
/** @type {!shaka.ui.Overlay} */
|
||||
let ui = this.video_['ui'];
|
||||
goog.asserts.assert(ui, 'UI should be available!');
|
||||
|
||||
this.controlsElement_ = document.getElementById('controls');
|
||||
this.pauseIcon_ = document.getElementById('pauseIcon');
|
||||
this.player_ = ui.getPlayer();
|
||||
goog.asserts.assert(this.player_, 'Player should be available!');
|
||||
|
||||
let videoContainer = /** @type {HTMLElement} */
|
||||
(document.getElementById('videoContainer'));
|
||||
goog.asserts.assert(videoContainer, 'Video container should be available!');
|
||||
|
||||
this.controlsElement_ = shaka.ui.Utils.getFirstDescendantWithClassName(
|
||||
videoContainer, 'controlsContainer');
|
||||
this.playButton_ = shaka.ui.Utils.getFirstDescendantWithClassName(
|
||||
videoContainer, 'playButtonContainer');
|
||||
this.idle_ = document.getElementById('idle');
|
||||
|
||||
this.video_.addEventListener(
|
||||
@@ -85,7 +98,8 @@ ShakaReceiver.prototype.init = function() {
|
||||
'emptied', this.onPlayStateChange_.bind(this));
|
||||
|
||||
this.receiver_ = new shaka.cast.CastReceiver(
|
||||
this.video_, this.player_, this.appDataCallback_.bind(this));
|
||||
this.video_, /** @type {!shaka.Player} */ (this.player_),
|
||||
this.appDataCallback_.bind(this));
|
||||
this.receiver_.addEventListener(
|
||||
'caststatuschanged', this.checkIdle_.bind(this));
|
||||
|
||||
@@ -157,9 +171,9 @@ ShakaReceiver.prototype.onPlayStateChange_ = function() {
|
||||
}
|
||||
|
||||
if (this.video_.paused) {
|
||||
this.pauseIcon_.textContent = 'pause';
|
||||
this.playButton_.style.display = 'inline';
|
||||
} else {
|
||||
this.pauseIcon_.textContent = 'play_arrow';
|
||||
this.playButton_.style.display = 'none';
|
||||
}
|
||||
|
||||
if (this.video_.paused && this.video_.readyState > 0) {
|
||||
@@ -184,9 +198,4 @@ function receiverAppInit() {
|
||||
}
|
||||
|
||||
|
||||
if (document.readyState == 'loading' ||
|
||||
document.readyState == 'interactive') {
|
||||
window.addEventListener('load', receiverAppInit);
|
||||
} else {
|
||||
receiverAppInit();
|
||||
}
|
||||
document.addEventListener('shaka-ui-loaded', receiverAppInit);
|
||||
|
||||
@@ -47,6 +47,7 @@ shakaAssets.Encoder = {
|
||||
|
||||
/** @enum {string} */
|
||||
shakaAssets.Source = {
|
||||
CUSTOM: 'Custom',
|
||||
SHAKA: 'Shaka',
|
||||
AXINOM: 'Axinom',
|
||||
UNIFIED_STREAMING: 'Unified Streaming',
|
||||
@@ -103,6 +104,7 @@ shakaAssets.Feature = {
|
||||
|
||||
SURROUND: 'surround sound',
|
||||
|
||||
DASH: 'DASH',
|
||||
HLS: 'HLS',
|
||||
};
|
||||
|
||||
@@ -130,6 +132,23 @@ shakaAssets.Feature = {
|
||||
shakaAssets.ExtraText;
|
||||
|
||||
|
||||
/**
|
||||
* @typedef {{
|
||||
* icon: string,
|
||||
* shortName: string,
|
||||
* description: string
|
||||
* }}
|
||||
*
|
||||
* @property {string} icon
|
||||
* An URI pointing to an icon.
|
||||
* @property {string} shortName
|
||||
* A shorter, snappier name for the asset.
|
||||
* @property {string} description
|
||||
* A line or two of text describing the asset.
|
||||
*/
|
||||
shakaAssets.FeaturedInfo;
|
||||
|
||||
|
||||
/**
|
||||
* @typedef {{
|
||||
* name: string,
|
||||
@@ -137,6 +156,7 @@ shakaAssets.ExtraText;
|
||||
* certificateUri: (string|undefined),
|
||||
* focus: (boolean|undefined),
|
||||
* disabled: (boolean|undefined),
|
||||
* featuredInfo: (shakaAssets.FeaturedInfo|undefined),
|
||||
* extraText: (!Array.<shakaAssets.ExtraText>|undefined),
|
||||
*
|
||||
* encoder: shakaAssets.Encoder,
|
||||
@@ -167,6 +187,10 @@ shakaAssets.ExtraText;
|
||||
* @property {(boolean|undefined)} disabled
|
||||
* (optional) If true, disables tests for this asset and hides it in the demo
|
||||
* app.
|
||||
* @property {(shakaAssets.FeaturedInfo|undefined)} featuredInfo
|
||||
* (optional) A structure containing data for the featured panel for this
|
||||
* asset in the demo app's main panel.
|
||||
* If not set, this asset will not appear in the main panel.
|
||||
* @property {(!Array.<shakaAssets.ExtraText>|undefined)} extraText
|
||||
* (optional) An array of extra text sources (e.g. external captions).
|
||||
*
|
||||
@@ -261,6 +285,7 @@ shakaAssets.testAssets = [
|
||||
source: shakaAssets.Source.SHAKA,
|
||||
drm: [],
|
||||
features: [
|
||||
shakaAssets.Feature.DASH,
|
||||
shakaAssets.Feature.MP4,
|
||||
shakaAssets.Feature.MULTIPLE_LANGUAGES,
|
||||
shakaAssets.Feature.SEGMENT_BASE,
|
||||
@@ -277,6 +302,7 @@ shakaAssets.testAssets = [
|
||||
source: shakaAssets.Source.SHAKA,
|
||||
drm: [shakaAssets.KeySystem.WIDEVINE],
|
||||
features: [
|
||||
shakaAssets.Feature.DASH,
|
||||
shakaAssets.Feature.MP4,
|
||||
shakaAssets.Feature.MULTIPLE_LANGUAGES,
|
||||
shakaAssets.Feature.SEGMENT_BASE,
|
||||
@@ -297,6 +323,7 @@ shakaAssets.testAssets = [
|
||||
source: shakaAssets.Source.SHAKA,
|
||||
drm: [shakaAssets.KeySystem.CLEAR_KEY],
|
||||
features: [
|
||||
shakaAssets.Feature.DASH,
|
||||
shakaAssets.Feature.MP4,
|
||||
shakaAssets.Feature.MULTIPLE_LANGUAGES,
|
||||
shakaAssets.Feature.SEGMENT_BASE,
|
||||
@@ -316,6 +343,11 @@ shakaAssets.testAssets = [
|
||||
encoder: shakaAssets.Encoder.SHAKA_PACKAGER,
|
||||
source: shakaAssets.Source.SHAKA,
|
||||
drm: [],
|
||||
featuredInfo: {
|
||||
icon: 'https://storage.googleapis.com/shaka-asset-icons/angel_one.png',
|
||||
shortName: 'Angel One',
|
||||
description: 'A classic Star Trek TNG episode, presented in HLS.',
|
||||
},
|
||||
features: [
|
||||
shakaAssets.Feature.HLS,
|
||||
shakaAssets.Feature.MP4,
|
||||
@@ -347,6 +379,7 @@ shakaAssets.testAssets = [
|
||||
source: shakaAssets.Source.SHAKA,
|
||||
drm: [],
|
||||
features: [
|
||||
shakaAssets.Feature.DASH,
|
||||
shakaAssets.Feature.HIGH_DEFINITION,
|
||||
shakaAssets.Feature.MP4,
|
||||
shakaAssets.Feature.SEGMENT_BASE,
|
||||
@@ -364,6 +397,7 @@ shakaAssets.testAssets = [
|
||||
source: shakaAssets.Source.SHAKA,
|
||||
drm: [],
|
||||
features: [
|
||||
shakaAssets.Feature.DASH,
|
||||
shakaAssets.Feature.HIGH_DEFINITION,
|
||||
shakaAssets.Feature.MP4,
|
||||
shakaAssets.Feature.SEGMENT_BASE,
|
||||
@@ -382,6 +416,7 @@ shakaAssets.testAssets = [
|
||||
source: shakaAssets.Source.SHAKA,
|
||||
drm: [],
|
||||
features: [
|
||||
shakaAssets.Feature.DASH,
|
||||
shakaAssets.Feature.HIGH_DEFINITION,
|
||||
shakaAssets.Feature.SEGMENT_BASE,
|
||||
shakaAssets.Feature.SUBTITLES,
|
||||
@@ -398,6 +433,7 @@ shakaAssets.testAssets = [
|
||||
source: shakaAssets.Source.SHAKA,
|
||||
drm: [],
|
||||
features: [
|
||||
shakaAssets.Feature.DASH,
|
||||
shakaAssets.Feature.HIGH_DEFINITION,
|
||||
shakaAssets.Feature.MP4,
|
||||
shakaAssets.Feature.SEGMENT_BASE,
|
||||
@@ -413,7 +449,14 @@ shakaAssets.testAssets = [
|
||||
encoder: shakaAssets.Encoder.SHAKA_PACKAGER,
|
||||
source: shakaAssets.Source.SHAKA,
|
||||
drm: [shakaAssets.KeySystem.WIDEVINE],
|
||||
featuredInfo: {
|
||||
icon: 'https://storage.googleapis.com/shaka-asset-icons/sintel.png',
|
||||
shortName: 'Sintel',
|
||||
description: 'A Blender Foundation short film, protected by Widevine ' +
|
||||
'encryption.',
|
||||
},
|
||||
features: [
|
||||
shakaAssets.Feature.DASH,
|
||||
shakaAssets.Feature.HIGH_DEFINITION,
|
||||
shakaAssets.Feature.MP4,
|
||||
shakaAssets.Feature.PSSH,
|
||||
@@ -436,6 +479,7 @@ shakaAssets.testAssets = [
|
||||
source: shakaAssets.Source.SHAKA,
|
||||
drm: [],
|
||||
features: [
|
||||
shakaAssets.Feature.DASH,
|
||||
shakaAssets.Feature.EMBEDDED_TEXT,
|
||||
shakaAssets.Feature.HIGH_DEFINITION,
|
||||
shakaAssets.Feature.MP4,
|
||||
@@ -454,6 +498,7 @@ shakaAssets.testAssets = [
|
||||
source: shakaAssets.Source.SHAKA,
|
||||
drm: [],
|
||||
features: [
|
||||
shakaAssets.Feature.DASH,
|
||||
shakaAssets.Feature.MP4,
|
||||
shakaAssets.Feature.MULTIPERIOD,
|
||||
shakaAssets.Feature.SEGMENT_BASE,
|
||||
@@ -468,6 +513,7 @@ shakaAssets.testAssets = [
|
||||
source: shakaAssets.Source.SHAKA,
|
||||
drm: [],
|
||||
features: [
|
||||
shakaAssets.Feature.DASH,
|
||||
shakaAssets.Feature.MP4,
|
||||
shakaAssets.Feature.MULTIPERIOD,
|
||||
shakaAssets.Feature.SEGMENT_BASE,
|
||||
@@ -486,7 +532,13 @@ shakaAssets.testAssets = [
|
||||
encoder: shakaAssets.Encoder.SHAKA_PACKAGER,
|
||||
source: shakaAssets.Source.SHAKA,
|
||||
drm: [],
|
||||
featuredInfo: {
|
||||
icon: 'https://storage.googleapis.com/shaka-asset-icons/audio_only.png',
|
||||
shortName: 'Dig the Uke',
|
||||
description: 'An audio-only presentation performed by Stefan Kartenberg.',
|
||||
},
|
||||
features: [
|
||||
shakaAssets.Feature.DASH,
|
||||
shakaAssets.Feature.MP4,
|
||||
shakaAssets.Feature.SEGMENT_BASE,
|
||||
shakaAssets.Feature.WEBM,
|
||||
@@ -504,6 +556,7 @@ shakaAssets.testAssets = [
|
||||
source: shakaAssets.Source.SHAKA,
|
||||
drm: [shakaAssets.KeySystem.WIDEVINE],
|
||||
features: [
|
||||
shakaAssets.Feature.DASH,
|
||||
shakaAssets.Feature.MP4,
|
||||
shakaAssets.Feature.SEGMENT_BASE,
|
||||
shakaAssets.Feature.WEBM,
|
||||
@@ -520,6 +573,7 @@ shakaAssets.testAssets = [
|
||||
source: shakaAssets.Source.SHAKA,
|
||||
drm: [],
|
||||
features: [
|
||||
shakaAssets.Feature.DASH,
|
||||
shakaAssets.Feature.HIGH_DEFINITION,
|
||||
shakaAssets.Feature.MP4,
|
||||
shakaAssets.Feature.SEGMENT_BASE,
|
||||
@@ -535,6 +589,7 @@ shakaAssets.testAssets = [
|
||||
source: shakaAssets.Source.SHAKA,
|
||||
drm: [],
|
||||
features: [
|
||||
shakaAssets.Feature.DASH,
|
||||
shakaAssets.Feature.MP4,
|
||||
shakaAssets.Feature.SEGMENT_BASE,
|
||||
shakaAssets.Feature.SURROUND,
|
||||
@@ -547,7 +602,13 @@ shakaAssets.testAssets = [
|
||||
encoder: shakaAssets.Encoder.SHAKA_PACKAGER,
|
||||
source: shakaAssets.Source.SHAKA,
|
||||
drm: [],
|
||||
featuredInfo: {
|
||||
icon: 'https://storage.googleapis.com/shaka-asset-icons/shaka.png',
|
||||
shortName: 'Shaka Player History',
|
||||
description: 'A self-indulgent DASH livestream.',
|
||||
},
|
||||
features: [
|
||||
shakaAssets.Feature.DASH,
|
||||
shakaAssets.Feature.HIGH_DEFINITION,
|
||||
shakaAssets.Feature.LIVE,
|
||||
shakaAssets.Feature.MP4,
|
||||
@@ -583,6 +644,7 @@ shakaAssets.testAssets = [
|
||||
shakaAssets.KeySystem.WIDEVINE,
|
||||
],
|
||||
features: [
|
||||
shakaAssets.Feature.DASH,
|
||||
shakaAssets.Feature.EMBEDDED_TEXT,
|
||||
shakaAssets.Feature.HIGH_DEFINITION,
|
||||
shakaAssets.Feature.MP4,
|
||||
@@ -611,6 +673,7 @@ shakaAssets.testAssets = [
|
||||
shakaAssets.KeySystem.WIDEVINE,
|
||||
],
|
||||
features: [
|
||||
shakaAssets.Feature.DASH,
|
||||
shakaAssets.Feature.EMBEDDED_TEXT,
|
||||
shakaAssets.Feature.HIGH_DEFINITION,
|
||||
shakaAssets.Feature.MP4,
|
||||
@@ -640,6 +703,7 @@ shakaAssets.testAssets = [
|
||||
shakaAssets.KeySystem.WIDEVINE,
|
||||
],
|
||||
features: [
|
||||
shakaAssets.Feature.DASH,
|
||||
shakaAssets.Feature.EMBEDDED_TEXT,
|
||||
shakaAssets.Feature.HIGH_DEFINITION,
|
||||
shakaAssets.Feature.MP4,
|
||||
@@ -667,6 +731,7 @@ shakaAssets.testAssets = [
|
||||
source: shakaAssets.Source.AXINOM,
|
||||
drm: [],
|
||||
features: [
|
||||
shakaAssets.Feature.DASH,
|
||||
shakaAssets.Feature.EMBEDDED_TEXT,
|
||||
shakaAssets.Feature.HIGH_DEFINITION,
|
||||
shakaAssets.Feature.MP4,
|
||||
@@ -684,6 +749,7 @@ shakaAssets.testAssets = [
|
||||
source: shakaAssets.Source.AXINOM,
|
||||
drm: [],
|
||||
features: [
|
||||
shakaAssets.Feature.DASH,
|
||||
shakaAssets.Feature.EMBEDDED_TEXT,
|
||||
shakaAssets.Feature.HIGH_DEFINITION,
|
||||
shakaAssets.Feature.MP4,
|
||||
@@ -735,6 +801,7 @@ shakaAssets.testAssets = [
|
||||
source: shakaAssets.Source.UNIFIED_STREAMING,
|
||||
drm: [],
|
||||
features: [
|
||||
shakaAssets.Feature.DASH,
|
||||
shakaAssets.Feature.HIGH_DEFINITION,
|
||||
shakaAssets.Feature.MP4,
|
||||
shakaAssets.Feature.SEGMENT_TEMPLATE_TIMELINE,
|
||||
@@ -751,6 +818,7 @@ shakaAssets.testAssets = [
|
||||
shakaAssets.KeySystem.WIDEVINE,
|
||||
],
|
||||
features: [
|
||||
shakaAssets.Feature.DASH,
|
||||
shakaAssets.Feature.EMBEDDED_TEXT,
|
||||
shakaAssets.Feature.HIGH_DEFINITION,
|
||||
shakaAssets.Feature.MP4,
|
||||
@@ -774,6 +842,7 @@ shakaAssets.testAssets = [
|
||||
shakaAssets.KeySystem.PLAYREADY,
|
||||
],
|
||||
features: [
|
||||
shakaAssets.Feature.DASH,
|
||||
shakaAssets.Feature.EMBEDDED_TEXT,
|
||||
shakaAssets.Feature.HIGH_DEFINITION,
|
||||
shakaAssets.Feature.MP4,
|
||||
@@ -795,6 +864,7 @@ shakaAssets.testAssets = [
|
||||
source: shakaAssets.Source.UNIFIED_STREAMING,
|
||||
drm: [],
|
||||
features: [
|
||||
shakaAssets.Feature.DASH,
|
||||
shakaAssets.Feature.EMBEDDED_TEXT,
|
||||
shakaAssets.Feature.HIGH_DEFINITION,
|
||||
shakaAssets.Feature.MP4,
|
||||
@@ -829,6 +899,7 @@ shakaAssets.testAssets = [
|
||||
source: shakaAssets.Source.DASH_IF,
|
||||
drm: [],
|
||||
features: [
|
||||
shakaAssets.Feature.DASH,
|
||||
shakaAssets.Feature.LIVE,
|
||||
shakaAssets.Feature.MP4,
|
||||
shakaAssets.Feature.SEGMENT_TEMPLATE_TIMELINE,
|
||||
@@ -842,6 +913,7 @@ shakaAssets.testAssets = [
|
||||
source: shakaAssets.Source.DASH_IF,
|
||||
drm: [],
|
||||
features: [
|
||||
shakaAssets.Feature.DASH,
|
||||
shakaAssets.Feature.LIVE,
|
||||
shakaAssets.Feature.MP4,
|
||||
shakaAssets.Feature.SEGMENT_TEMPLATE_TIMELINE,
|
||||
@@ -855,6 +927,7 @@ shakaAssets.testAssets = [
|
||||
source: shakaAssets.Source.DASH_IF,
|
||||
drm: [],
|
||||
features: [
|
||||
shakaAssets.Feature.DASH,
|
||||
shakaAssets.Feature.LIVE,
|
||||
shakaAssets.Feature.MP4,
|
||||
shakaAssets.Feature.MULTIPERIOD,
|
||||
@@ -873,6 +946,7 @@ shakaAssets.testAssets = [
|
||||
source: shakaAssets.Encoder.WOWZA,
|
||||
drm: [],
|
||||
features: [
|
||||
shakaAssets.Feature.DASH,
|
||||
shakaAssets.Feature.LIVE,
|
||||
shakaAssets.Feature.MP4,
|
||||
shakaAssets.Feature.SEGMENT_TEMPLATE_TIMELINE,
|
||||
@@ -891,6 +965,7 @@ shakaAssets.testAssets = [
|
||||
source: shakaAssets.Source.BITCODIN,
|
||||
drm: [],
|
||||
features: [
|
||||
shakaAssets.Feature.DASH,
|
||||
shakaAssets.Feature.HIGH_DEFINITION,
|
||||
shakaAssets.Feature.MP4,
|
||||
shakaAssets.Feature.SEGMENT_TEMPLATE_DURATION,
|
||||
@@ -938,6 +1013,7 @@ shakaAssets.testAssets = [
|
||||
source: shakaAssets.Source.NIMBLE_STREAMER,
|
||||
drm: [],
|
||||
features: [
|
||||
shakaAssets.Feature.DASH,
|
||||
shakaAssets.Feature.MP4,
|
||||
shakaAssets.Feature.SEGMENT_TEMPLATE_TIMELINE,
|
||||
],
|
||||
@@ -954,6 +1030,7 @@ shakaAssets.testAssets = [
|
||||
source: shakaAssets.Source.AZURE_MEDIA_SERVICES,
|
||||
drm: [],
|
||||
features: [
|
||||
shakaAssets.Feature.DASH,
|
||||
shakaAssets.Feature.MP4,
|
||||
shakaAssets.Feature.SEGMENT_TEMPLATE_TIMELINE,
|
||||
],
|
||||
@@ -969,6 +1046,7 @@ shakaAssets.testAssets = [
|
||||
shakaAssets.KeySystem.WIDEVINE,
|
||||
],
|
||||
features: [
|
||||
shakaAssets.Feature.DASH,
|
||||
shakaAssets.Feature.MP4,
|
||||
shakaAssets.Feature.SEGMENT_TEMPLATE_TIMELINE,
|
||||
],
|
||||
@@ -1006,6 +1084,7 @@ shakaAssets.testAssets = [
|
||||
source: shakaAssets.Source.AZURE_MEDIA_SERVICES,
|
||||
drm: [],
|
||||
features: [
|
||||
shakaAssets.Feature.DASH,
|
||||
shakaAssets.Feature.MP4,
|
||||
shakaAssets.Feature.SEGMENT_TEMPLATE_TIMELINE,
|
||||
shakaAssets.Feature.SUBTITLES,
|
||||
@@ -1031,6 +1110,7 @@ shakaAssets.testAssets = [
|
||||
source: shakaAssets.Source.GPAC,
|
||||
drm: [],
|
||||
features: [
|
||||
shakaAssets.Feature.DASH,
|
||||
shakaAssets.Feature.MP4,
|
||||
shakaAssets.Feature.SEGMENT_TEMPLATE_DURATION,
|
||||
],
|
||||
@@ -1043,6 +1123,7 @@ shakaAssets.testAssets = [
|
||||
source: shakaAssets.Source.GPAC,
|
||||
drm: [],
|
||||
features: [
|
||||
shakaAssets.Feature.DASH,
|
||||
shakaAssets.Feature.MP4,
|
||||
shakaAssets.Feature.MULTIPERIOD,
|
||||
shakaAssets.Feature.SEGMENT_TEMPLATE_DURATION,
|
||||
@@ -1056,6 +1137,7 @@ shakaAssets.testAssets = [
|
||||
source: shakaAssets.Source.GPAC,
|
||||
drm: [],
|
||||
features: [
|
||||
shakaAssets.Feature.DASH,
|
||||
shakaAssets.Feature.MP4,
|
||||
shakaAssets.Feature.SEGMENT_LIST_DURATION,
|
||||
],
|
||||
@@ -1073,6 +1155,7 @@ shakaAssets.testAssets = [
|
||||
source: shakaAssets.Source.GPAC,
|
||||
drm: [],
|
||||
features: [
|
||||
shakaAssets.Feature.DASH,
|
||||
shakaAssets.Feature.MP4,
|
||||
shakaAssets.Feature.SEGMENT_LIST_DURATION,
|
||||
],
|
||||
@@ -1085,6 +1168,7 @@ shakaAssets.testAssets = [
|
||||
source: shakaAssets.Source.GPAC,
|
||||
drm: [],
|
||||
features: [
|
||||
shakaAssets.Feature.DASH,
|
||||
shakaAssets.Feature.MP4,
|
||||
shakaAssets.Feature.SEGMENT_BASE,
|
||||
],
|
||||
@@ -1101,6 +1185,7 @@ shakaAssets.testAssets = [
|
||||
source: shakaAssets.Source.GPAC,
|
||||
drm: [],
|
||||
features: [
|
||||
shakaAssets.Feature.DASH,
|
||||
shakaAssets.Feature.MP4,
|
||||
shakaAssets.Feature.SEGMENT_TEMPLATE_DURATION,
|
||||
],
|
||||
@@ -1117,6 +1202,7 @@ shakaAssets.testAssets = [
|
||||
source: shakaAssets.Source.GPAC,
|
||||
drm: [],
|
||||
features: [
|
||||
shakaAssets.Feature.DASH,
|
||||
shakaAssets.Feature.MP4,
|
||||
shakaAssets.Feature.SEGMENT_TEMPLATE_DURATION,
|
||||
],
|
||||
@@ -1136,6 +1222,7 @@ shakaAssets.testAssets = [
|
||||
shakaAssets.KeySystem.WIDEVINE,
|
||||
],
|
||||
features: [
|
||||
shakaAssets.Feature.DASH,
|
||||
shakaAssets.Feature.MP4,
|
||||
shakaAssets.Feature.PSSH,
|
||||
shakaAssets.Feature.MULTIKEY,
|
||||
@@ -1162,6 +1249,7 @@ shakaAssets.testAssets = [
|
||||
shakaAssets.KeySystem.WIDEVINE,
|
||||
],
|
||||
features: [
|
||||
shakaAssets.Feature.DASH,
|
||||
shakaAssets.Feature.MP4,
|
||||
shakaAssets.Feature.PSSH,
|
||||
shakaAssets.Feature.MULTIKEY,
|
||||
@@ -1186,6 +1274,7 @@ shakaAssets.testAssets = [
|
||||
shakaAssets.KeySystem.WIDEVINE,
|
||||
],
|
||||
features: [
|
||||
shakaAssets.Feature.DASH,
|
||||
shakaAssets.Feature.MP4,
|
||||
shakaAssets.Feature.PSSH,
|
||||
shakaAssets.Feature.MULTIKEY,
|
||||
@@ -1211,6 +1300,7 @@ shakaAssets.testAssets = [
|
||||
shakaAssets.KeySystem.WIDEVINE,
|
||||
],
|
||||
features: [
|
||||
shakaAssets.Feature.DASH,
|
||||
shakaAssets.Feature.MP4,
|
||||
shakaAssets.Feature.MULTIPLE_LANGUAGES,
|
||||
shakaAssets.Feature.SEGMENT_LIST_DURATION,
|
||||
|
||||
@@ -1,362 +0,0 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#bufferingSpinner {
|
||||
margin: auto;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
#controlsContainer {
|
||||
width: 100%;
|
||||
height: 35px;
|
||||
padding: 0 5px;
|
||||
margin: 0 auto 5px;
|
||||
box-sizing: border-box;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
bottom: 5px;
|
||||
}
|
||||
|
||||
#controls {
|
||||
width: 100%;
|
||||
max-width: 800px;
|
||||
height: 35px;
|
||||
margin: 0;
|
||||
padding: 0 0 0 7px;
|
||||
|
||||
background-color: rgba(20, 20, 20, 0.8);
|
||||
border-radius: 5px;
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s;
|
||||
}
|
||||
|
||||
/* Show the controls when the mouse is over them. This overrides the mouse
|
||||
* timeout logic in JS that is used to hide the controls when the mouse stops
|
||||
* moving over the video container.
|
||||
*/
|
||||
#controls:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* NOTE: These fullscreen pseudo-classes can't be combined. Browsers ignore
|
||||
* the rest of the list once they hit one prefix they don't support.
|
||||
*/
|
||||
#videoContainer:fullscreen { width: 100%; height: 100%; }
|
||||
#videoContainer:-webkit-full-screen { width: 100%; height: 100%; }
|
||||
#videoContainer:-moz-full-screen { width: 100%; height: 100%; }
|
||||
#videoContainer:-ms-fullscreen { width: 100%; height: 100%; }
|
||||
|
||||
#controls button {
|
||||
color: white;
|
||||
height: 32px;
|
||||
width: 32px;
|
||||
padding: 0;
|
||||
margin: 0 7px 0 0;
|
||||
background: transparent;
|
||||
border: 0;
|
||||
outline: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#controls button:active {
|
||||
background: rgba(100, 100, 100, 0.4);
|
||||
}
|
||||
|
||||
#controls button:disabled {
|
||||
color: rgba(255, 255, 255, 0.3);
|
||||
}
|
||||
|
||||
#controls input[type="range"] {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#castReceiverName {
|
||||
display: none;
|
||||
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
color: white;
|
||||
font-size: 150%;
|
||||
padding: 5px;
|
||||
|
||||
bottom: 50px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
margin: auto;
|
||||
width: max-content;
|
||||
}
|
||||
|
||||
#giantPlayButtonContainer {
|
||||
margin: auto;
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
#giantPlayButton {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
font-size: 150px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
outline: none;
|
||||
background: rgba(100, 100, 100, 0.4);
|
||||
border-radius: 40px;
|
||||
}
|
||||
|
||||
#pauseButton, #unmuteButton,
|
||||
#castButton, #castConnectedButton,
|
||||
#rewindButton, #fastForwardButton,
|
||||
#giantPlayButtonContainer, #bufferingSpinner {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#currentTime {
|
||||
display: flex;
|
||||
flex-grow: 0;
|
||||
margin: 0 9px 0 0;
|
||||
font-family: sans-serif;
|
||||
font-size: 13px;
|
||||
font-weight: bold;
|
||||
color: white;
|
||||
|
||||
cursor: default;
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
}
|
||||
|
||||
|
||||
/* Always show controls while casting */
|
||||
#controls.casting {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* Hide fullscreen button while casting */
|
||||
#controls.casting #fullscreenButton {
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
||||
/* NOTE: pseudo-elements for different browsers can't be combined with commas.
|
||||
* Browsers will ignore styles if any pseudo-element in the list is unknown.
|
||||
*/
|
||||
|
||||
/* range inputs, common style */
|
||||
#seekBar, #volumeBar {
|
||||
display: flex;
|
||||
height: 7px;
|
||||
margin: 0 12px 0 0;
|
||||
padding: 0;
|
||||
|
||||
/* removes webkit default styling */
|
||||
-webkit-appearance: none;
|
||||
|
||||
border: 1px solid #666;
|
||||
border-radius: 4px;
|
||||
background-color: black;
|
||||
outline: none;
|
||||
}
|
||||
/* removes mozilla default styling */
|
||||
#seekBar::-moz-range-track, #volumeBar::-moz-range-track {
|
||||
background-color: transparent;
|
||||
outline: none;
|
||||
}
|
||||
/* removes IE default styling */
|
||||
#seekBar::-ms-track, #seekBar::-ms-fill-lower, #seekBar::-ms-fill-upper,
|
||||
#volumeBar::-ms-track, #volumeBar::-ms-fill-lower, #volumeBar::-ms-fill-upper {
|
||||
background-color: transparent;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
/* per-instance styles */
|
||||
#seekBar {
|
||||
flex-grow: 1;
|
||||
}
|
||||
#volumeBar {
|
||||
flex-grow: 0;
|
||||
min-width: 15px;
|
||||
max-width: 70px;
|
||||
}
|
||||
|
||||
|
||||
/* thumb pseudo-element, common style */
|
||||
#seekBar::-webkit-slider-thumb, #volumeBar::-webkit-slider-thumb {
|
||||
-webkit-appearance: none;
|
||||
background-color: white;
|
||||
outline: none;
|
||||
}
|
||||
#seekBar::-moz-range-thumb, #volumeBar::-moz-range-thumb {
|
||||
background-color: white;
|
||||
outline: none;
|
||||
}
|
||||
#seekBar::-ms-thumb, #volumeBar::-ms-thumb {
|
||||
background-color: white;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
/* thumb pseudo-element, seek style */
|
||||
#seekBar::-webkit-slider-thumb {
|
||||
width: 18px;
|
||||
height: 11px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
#seekBar::-moz-range-thumb {
|
||||
width: 18px;
|
||||
height: 11px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
#seekBar::-ms-thumb {
|
||||
width: 18px;
|
||||
height: 11px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
/* thumb pseudo-element, volume style */
|
||||
#volumeBar::-webkit-slider-thumb {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border-radius: 12px;
|
||||
}
|
||||
#volumeBar::-moz-range-thumb {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border-radius: 12px;
|
||||
}
|
||||
#volumeBar::-ms-thumb {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
/* turn off tooltips for the seekBar on IE */
|
||||
#seekBar::-ms-tooltip {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* hide volume and mute buttons on mobile-sized screens */
|
||||
@media screen and (max-width: 700px) {
|
||||
#volumeBar, #muteButton {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
The SVG/CSS buffering spinner is based on http://codepen.io/jczimm/pen/vEBpoL
|
||||
Some local modifications have been made.
|
||||
|
||||
Copyright (c) 2016 by jczimm
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do
|
||||
so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
|
||||
.spinnerSvg {
|
||||
animation: rotate 2s linear infinite;
|
||||
height: 100%;
|
||||
transform-origin: center center;
|
||||
width: 100%;
|
||||
position: absolute;
|
||||
top: 0; bottom: 0; left: 0; right: 0;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.spinnerPath {
|
||||
/* Fall back for IE 11, where the stroke properties are not animated,
|
||||
but the spinner still rotates. */
|
||||
stroke: #d62d20;
|
||||
stroke-dasharray: 20, 200;
|
||||
stroke-dashoffset: 0;
|
||||
|
||||
animation:
|
||||
dash 1.5s ease-in-out infinite,
|
||||
color 6s ease-in-out infinite;
|
||||
stroke-linecap: round;
|
||||
}
|
||||
|
||||
@keyframes rotate {
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes dash {
|
||||
0% {
|
||||
stroke-dasharray: 1, 200;
|
||||
stroke-dashoffset: 0;
|
||||
}
|
||||
50% {
|
||||
stroke-dasharray: 89, 200;
|
||||
stroke-dashoffset: -35px;
|
||||
}
|
||||
100% {
|
||||
stroke-dasharray: 89, 200;
|
||||
stroke-dashoffset: -124px;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes color {
|
||||
100%, 0% {
|
||||
stroke: #d62d20;
|
||||
}
|
||||
40% {
|
||||
stroke: #0057e7;
|
||||
}
|
||||
66% {
|
||||
stroke: #008744;
|
||||
}
|
||||
80%, 90% {
|
||||
stroke: #ffa700;
|
||||
}
|
||||
}
|
||||
Vendored
-809
@@ -1,809 +0,0 @@
|
||||
/**
|
||||
* @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.
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* A container for custom video controls.
|
||||
* @constructor
|
||||
* @suppress {missingProvide}
|
||||
*/
|
||||
function ShakaControls() {
|
||||
/** @private {boolean} */
|
||||
this.enabled_ = true;
|
||||
|
||||
/** @private {shaka.cast.CastProxy} */
|
||||
this.castProxy_ = null;
|
||||
|
||||
/** @private {boolean} */
|
||||
this.castAllowed_ = true;
|
||||
|
||||
/** @private {?function(!shaka.util.Error)} */
|
||||
this.onError_ = null;
|
||||
|
||||
/** @private {HTMLMediaElement} */
|
||||
this.video_ = null;
|
||||
|
||||
/** @private {shaka.Player} */
|
||||
this.player_ = null;
|
||||
|
||||
/** @private {Element} */
|
||||
this.videoContainer_ = document.getElementById('videoContainer');
|
||||
|
||||
/** @private {Element} */
|
||||
this.controls_ = document.getElementById('controls');
|
||||
|
||||
/** @private {Element} */
|
||||
this.playPauseButton_ = document.getElementById('playPauseButton');
|
||||
|
||||
/** @private {Element} */
|
||||
this.seekBar_ = document.getElementById('seekBar');
|
||||
|
||||
/** @private {Element} */
|
||||
this.muteButton_ = document.getElementById('muteButton');
|
||||
|
||||
/** @private {Element} */
|
||||
this.volumeBar_ = document.getElementById('volumeBar');
|
||||
|
||||
/** @private {Element} */
|
||||
this.captionButton_ = document.getElementById('captionButton');
|
||||
|
||||
/** @private {Element} */
|
||||
this.fullscreenButton_ = document.getElementById('fullscreenButton');
|
||||
|
||||
/** @private {Element} */
|
||||
this.currentTime_ = document.getElementById('currentTime');
|
||||
|
||||
/** @private {Element} */
|
||||
this.rewindButton_ = document.getElementById('rewindButton');
|
||||
|
||||
/** @private {Element} */
|
||||
this.fastForwardButton_ = document.getElementById('fastForwardButton');
|
||||
|
||||
/** @private {Element} */
|
||||
this.castButton_ = document.getElementById('castButton');
|
||||
|
||||
/** @private {Element} */
|
||||
this.castReceiverName_ = document.getElementById('castReceiverName');
|
||||
|
||||
/** @private {Element} */
|
||||
this.bufferingSpinner_ = document.getElementById('bufferingSpinner');
|
||||
|
||||
/** @private {Element} */
|
||||
this.giantPlayButtonContainer_ =
|
||||
document.getElementById('giantPlayButtonContainer');
|
||||
|
||||
/** @private {boolean} */
|
||||
this.isSeeking_ = false;
|
||||
|
||||
/** @private {number} */
|
||||
this.trickPlayRate_ = 1;
|
||||
|
||||
/** @private {?number} */
|
||||
this.seekTimeoutId_ = null;
|
||||
|
||||
/** @private {?number} */
|
||||
this.mouseStillTimeoutId_ = null;
|
||||
|
||||
/** @private {?number} */
|
||||
this.lastTouchEventTime_ = null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Initializes the player controls.
|
||||
* @param {shaka.cast.CastProxy} castProxy
|
||||
* @param {function(!shaka.util.Error)} onError
|
||||
* @param {function(boolean)} notifyCastStatus
|
||||
*/
|
||||
ShakaControls.prototype.init = function(castProxy, onError, notifyCastStatus) {
|
||||
this.castProxy_ = castProxy;
|
||||
this.onError_ = onError;
|
||||
this.notifyCastStatus_ = notifyCastStatus;
|
||||
this.initMinimal(castProxy.getVideo(), castProxy.getPlayer());
|
||||
|
||||
this.playPauseButton_.addEventListener(
|
||||
'click', this.onPlayPauseClick_.bind(this));
|
||||
this.video_.addEventListener(
|
||||
'play', this.onPlayStateChange_.bind(this));
|
||||
this.video_.addEventListener(
|
||||
'pause', this.onPlayStateChange_.bind(this));
|
||||
|
||||
// Since videos go into a paused state at the end, Chrome and Edge both fire
|
||||
// the 'pause' event when a video ends. IE 11 only fires the 'ended' event.
|
||||
this.video_.addEventListener(
|
||||
'ended', this.onPlayStateChange_.bind(this));
|
||||
|
||||
this.seekBar_.addEventListener(
|
||||
'mousedown', this.onSeekStart_.bind(this));
|
||||
this.seekBar_.addEventListener(
|
||||
'touchstart', this.onSeekStart_.bind(this), {passive: true});
|
||||
this.seekBar_.addEventListener(
|
||||
'input', this.onSeekInput_.bind(this));
|
||||
this.seekBar_.addEventListener(
|
||||
'touchend', this.onSeekEnd_.bind(this));
|
||||
this.seekBar_.addEventListener(
|
||||
'mouseup', this.onSeekEnd_.bind(this));
|
||||
|
||||
this.muteButton_.addEventListener(
|
||||
'click', this.onMuteClick_.bind(this));
|
||||
|
||||
this.volumeBar_.addEventListener(
|
||||
'input', this.onVolumeInput_.bind(this));
|
||||
this.video_.addEventListener(
|
||||
'volumechange', this.onVolumeStateChange_.bind(this));
|
||||
// Initialize volume display with a fake event.
|
||||
this.onVolumeStateChange_();
|
||||
|
||||
this.captionButton_.addEventListener(
|
||||
'click', this.onCaptionClick_.bind(this));
|
||||
this.player_.addEventListener(
|
||||
'texttrackvisibility', this.onCaptionStateChange_.bind(this));
|
||||
this.player_.addEventListener(
|
||||
'trackschanged', this.onTracksChange_.bind(this));
|
||||
// Initialize caption state with a fake event.
|
||||
this.onCaptionStateChange_();
|
||||
|
||||
this.fullscreenButton_.addEventListener(
|
||||
'click', this.onFullscreenClick_.bind(this));
|
||||
|
||||
this.currentTime_.addEventListener(
|
||||
'click', this.onCurrentTimeClick_.bind(this));
|
||||
|
||||
this.rewindButton_.addEventListener(
|
||||
'click', this.onRewindClick_.bind(this));
|
||||
this.fastForwardButton_.addEventListener(
|
||||
'click', this.onFastForwardClick_.bind(this));
|
||||
|
||||
this.castButton_.addEventListener(
|
||||
'click', this.onCastClick_.bind(this));
|
||||
|
||||
this.videoContainer_.addEventListener(
|
||||
'touchstart', this.onContainerTouch_.bind(this), {passive: false});
|
||||
this.videoContainer_.addEventListener(
|
||||
'click', this.onContainerClick_.bind(this));
|
||||
|
||||
// Clicks in the controls should not propagate up to the video container.
|
||||
this.controls_.addEventListener(
|
||||
'click', function(event) { event.stopPropagation(); });
|
||||
|
||||
this.videoContainer_.addEventListener(
|
||||
'mousemove', this.onMouseMove_.bind(this));
|
||||
this.videoContainer_.addEventListener(
|
||||
'touchmove', this.onMouseMove_.bind(this), {passive: true});
|
||||
this.videoContainer_.addEventListener(
|
||||
'touchend', this.onMouseMove_.bind(this), {passive: true});
|
||||
this.videoContainer_.addEventListener(
|
||||
'mouseout', this.onMouseOut_.bind(this));
|
||||
|
||||
this.castProxy_.addEventListener(
|
||||
'caststatuschanged', this.onCastStatusChange_.bind(this));
|
||||
|
||||
if (screen.orientation) {
|
||||
screen.orientation.addEventListener(
|
||||
'change', this.onScreenRotation_.bind(this));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* When a mobile device is rotated to landscape layout, and the video is loaded,
|
||||
* make the demo app go into fullscreen.
|
||||
* Similarly, exit fullscreen when the device is rotated to portrait layout.
|
||||
* @private
|
||||
*/
|
||||
ShakaControls.prototype.onScreenRotation_ = function() {
|
||||
if (!this.video_ ||
|
||||
this.video_.readyState == 0 ||
|
||||
this.castProxy_.isCasting()) return;
|
||||
|
||||
if (screen.orientation.type.includes('landscape') &&
|
||||
!document.fullscreenElement) {
|
||||
this.videoContainer_.requestFullscreen();
|
||||
} else if (screen.orientation.type.includes('portrait') &&
|
||||
document.fullscreenElement) {
|
||||
document.exitFullscreen();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Initializes minimal player controls. Used on both sender (indirectly) and
|
||||
* receiver (directly).
|
||||
* @param {HTMLMediaElement} video
|
||||
* @param {shaka.Player} player
|
||||
*/
|
||||
ShakaControls.prototype.initMinimal = function(video, player) {
|
||||
this.video_ = video;
|
||||
this.player_ = player;
|
||||
this.player_.addEventListener(
|
||||
'buffering', this.onBufferingStateChange_.bind(this));
|
||||
window.setInterval(this.updateTimeAndSeekRange_.bind(this), 125);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* This allows the application to inhibit casting.
|
||||
*
|
||||
* @param {boolean} allow
|
||||
*/
|
||||
ShakaControls.prototype.allowCast = function(allow) {
|
||||
this.castAllowed_ = allow;
|
||||
this.onCastStatusChange_(null);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Used by the application to notify the controls that a load operation is
|
||||
* complete. This allows the controls to recalculate play/paused state, which
|
||||
* is important for platforms like Android where autoplay is disabled.
|
||||
*/
|
||||
ShakaControls.prototype.loadComplete = function() {
|
||||
// If we are on Android or if autoplay is false, video.paused should be true.
|
||||
// Otherwise, video.paused is false and the content is autoplaying.
|
||||
this.onPlayStateChange_();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Enable or disable the custom controls.
|
||||
* Disabling custom controls enables native controls.
|
||||
*
|
||||
* @param {boolean} enabled
|
||||
*/
|
||||
ShakaControls.prototype.setEnabled = function(enabled) {
|
||||
this.enabled_ = enabled;
|
||||
if (enabled) {
|
||||
this.controls_.parentElement.style.display = 'inherit';
|
||||
this.video_.controls = false;
|
||||
} else {
|
||||
this.controls_.parentElement.style.display = 'none';
|
||||
this.video_.controls = true;
|
||||
}
|
||||
|
||||
// The effects of play state changes are inhibited while showing native
|
||||
// browser controls. Recalculate that state now.
|
||||
this.onPlayStateChange_();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Hiding the cursor when the mouse stops moving seems to be the only decent UX
|
||||
* in fullscreen mode. Since we can't use pure CSS for that, we use events both
|
||||
* in and out of fullscreen mode.
|
||||
* @param {!Event} event
|
||||
* @private
|
||||
*/
|
||||
ShakaControls.prototype.onMouseMove_ = function(event) {
|
||||
if (event.type == 'touchstart' || event.type == 'touchmove' ||
|
||||
event.type == 'touchend') {
|
||||
this.lastTouchEventTime_ = Date.now();
|
||||
} else if (this.lastTouchEventTime_ + 1000 < Date.now()) {
|
||||
// It has been a while since the last touch event, this is probably a real
|
||||
// mouse moving, so treat it like a mouse.
|
||||
this.lastTouchEventTime_ = null;
|
||||
}
|
||||
|
||||
// When there is a touch, we can get a 'mousemove' event after touch events.
|
||||
// This should be treated as part of the touch, which has already been handled
|
||||
if (this.lastTouchEventTime_ && event.type == 'mousemove') {
|
||||
return;
|
||||
}
|
||||
|
||||
// Use the cursor specified in the CSS file.
|
||||
this.videoContainer_.style.cursor = '';
|
||||
// Show the controls.
|
||||
this.controls_.style.opacity = 1;
|
||||
this.updateTimeAndSeekRange_();
|
||||
|
||||
// Hide the cursor when the mouse stops moving.
|
||||
// Only applies while the cursor is over the video container.
|
||||
if (this.mouseStillTimeoutId_) {
|
||||
// Reset the timer.
|
||||
window.clearTimeout(this.mouseStillTimeoutId_);
|
||||
}
|
||||
|
||||
// Only start a timeout on 'touchend' or for 'mousemove' with no touch events.
|
||||
if (event.type == 'touchend' || !this.lastTouchEventTime_) {
|
||||
this.mouseStillTimeoutId_ = window.setTimeout(
|
||||
this.onMouseStill_.bind(this), 3000);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/** @private */
|
||||
ShakaControls.prototype.onMouseOut_ = function() {
|
||||
// We sometimes get 'mouseout' events with touches. Since we can never leave
|
||||
// the video element when touching, ignore.
|
||||
if (this.lastTouchEventTime_) return;
|
||||
|
||||
// Expire the timer early.
|
||||
if (this.mouseStillTimeoutId_) {
|
||||
window.clearTimeout(this.mouseStillTimeoutId_);
|
||||
}
|
||||
// Run the timeout callback to hide the controls.
|
||||
// If we don't, the opacity style we set in onMouseMove_ will continue to
|
||||
// override the opacity in CSS and force the controls to stay visible.
|
||||
this.onMouseStill_();
|
||||
};
|
||||
|
||||
|
||||
/** @private */
|
||||
ShakaControls.prototype.onMouseStill_ = function() {
|
||||
// The mouse has stopped moving.
|
||||
this.mouseStillTimeoutId_ = null;
|
||||
// Hide the cursor. (NOTE: not supported on IE)
|
||||
this.videoContainer_.style.cursor = 'none';
|
||||
// Revert opacity control to CSS. Hovering directly over the controls will
|
||||
// keep them showing, even in fullscreen mode. Unless there were touch events,
|
||||
// then override the hover and hide the controls.
|
||||
this.controls_.style.opacity = this.lastTouchEventTime_ ? '0' : '';
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {!Event} event
|
||||
* @private
|
||||
*/
|
||||
ShakaControls.prototype.onContainerTouch_ = function(event) {
|
||||
if (!this.video_.duration) {
|
||||
// Can't play yet. Ignore.
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.controls_.style.opacity == 1) {
|
||||
this.lastTouchEventTime_ = Date.now();
|
||||
// The controls are showing.
|
||||
// Let this event continue and become a click.
|
||||
} else {
|
||||
// The controls are hidden, so show them.
|
||||
this.onMouseMove_(event);
|
||||
// Stop this event from becoming a click event.
|
||||
event.preventDefault();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {!Event} event
|
||||
* @private
|
||||
*/
|
||||
ShakaControls.prototype.onContainerClick_ = function(event) {
|
||||
if (!this.enabled_) return;
|
||||
|
||||
this.onPlayPauseClick_();
|
||||
};
|
||||
|
||||
|
||||
/** @private */
|
||||
ShakaControls.prototype.onPlayPauseClick_ = function() {
|
||||
if (!this.enabled_) return;
|
||||
|
||||
if (!this.video_.duration) {
|
||||
// Can't play yet. Ignore.
|
||||
return;
|
||||
}
|
||||
|
||||
this.player_.cancelTrickPlay();
|
||||
this.trickPlayRate_ = 1;
|
||||
|
||||
if (this.video_.paused) {
|
||||
this.video_.play();
|
||||
} else {
|
||||
this.video_.pause();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/** @private */
|
||||
ShakaControls.prototype.onPlayStateChange_ = function() {
|
||||
// On IE 11, a video may end without going into a paused state. To correct
|
||||
// both the UI state and the state of the video tag itself, we explicitly
|
||||
// pause the video if that happens.
|
||||
if (this.video_.ended && !this.video_.paused) {
|
||||
this.video_.pause();
|
||||
}
|
||||
|
||||
// Video is paused during seek, so don't show the play arrow while seeking:
|
||||
if (this.enabled_ && this.video_.paused && !this.isSeeking_) {
|
||||
this.playPauseButton_.textContent = 'play_arrow';
|
||||
this.giantPlayButtonContainer_.style.display = 'inline';
|
||||
} else {
|
||||
this.playPauseButton_.textContent = 'pause';
|
||||
this.giantPlayButtonContainer_.style.display = 'none';
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/** @private */
|
||||
ShakaControls.prototype.onSeekStart_ = function() {
|
||||
if (!this.enabled_) return;
|
||||
|
||||
this.isSeeking_ = true;
|
||||
this.video_.pause();
|
||||
};
|
||||
|
||||
|
||||
/** @private */
|
||||
ShakaControls.prototype.onSeekInput_ = function() {
|
||||
if (!this.enabled_) return;
|
||||
|
||||
if (!this.video_.duration) {
|
||||
// Can't seek yet. Ignore.
|
||||
return;
|
||||
}
|
||||
|
||||
// Update the UI right away.
|
||||
this.updateTimeAndSeekRange_();
|
||||
|
||||
// Collect input events and seek when things have been stable for 125ms.
|
||||
if (this.seekTimeoutId_ != null) {
|
||||
window.clearTimeout(this.seekTimeoutId_);
|
||||
}
|
||||
this.seekTimeoutId_ = window.setTimeout(
|
||||
this.onSeekInputTimeout_.bind(this), 125);
|
||||
};
|
||||
|
||||
|
||||
/** @private */
|
||||
ShakaControls.prototype.onSeekInputTimeout_ = function() {
|
||||
this.seekTimeoutId_ = null;
|
||||
this.video_.currentTime = parseFloat(this.seekBar_.value);
|
||||
};
|
||||
|
||||
|
||||
/** @private */
|
||||
ShakaControls.prototype.onSeekEnd_ = function() {
|
||||
if (!this.enabled_) return;
|
||||
|
||||
if (this.seekTimeoutId_ != null) {
|
||||
// They just let go of the seek bar, so end the timer early.
|
||||
window.clearTimeout(this.seekTimeoutId_);
|
||||
this.onSeekInputTimeout_();
|
||||
}
|
||||
|
||||
this.isSeeking_ = false;
|
||||
this.video_.play();
|
||||
};
|
||||
|
||||
|
||||
/** @private */
|
||||
ShakaControls.prototype.onMuteClick_ = function() {
|
||||
if (!this.enabled_) return;
|
||||
|
||||
this.video_.muted = !this.video_.muted;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Updates the controls to reflect volume changes.
|
||||
* @private
|
||||
*/
|
||||
ShakaControls.prototype.onVolumeStateChange_ = function() {
|
||||
if (this.video_.muted) {
|
||||
this.muteButton_.textContent = 'volume_off';
|
||||
this.volumeBar_.value = 0;
|
||||
} else {
|
||||
this.muteButton_.textContent = 'volume_up';
|
||||
this.volumeBar_.value = this.video_.volume;
|
||||
}
|
||||
|
||||
let gradient = ['to right'];
|
||||
gradient.push('#ccc ' + (this.volumeBar_.value * 100) + '%');
|
||||
gradient.push('#000 ' + (this.volumeBar_.value * 100) + '%');
|
||||
gradient.push('#000 100%');
|
||||
this.volumeBar_.style.background =
|
||||
'linear-gradient(' + gradient.join(',') + ')';
|
||||
};
|
||||
|
||||
|
||||
/** @private */
|
||||
ShakaControls.prototype.onVolumeInput_ = function() {
|
||||
this.video_.volume = parseFloat(this.volumeBar_.value);
|
||||
this.video_.muted = false;
|
||||
};
|
||||
|
||||
|
||||
/** @private */
|
||||
ShakaControls.prototype.onCaptionClick_ = function() {
|
||||
if (!this.enabled_) return;
|
||||
|
||||
this.player_.setTextTrackVisibility(!this.player_.isTextTrackVisible());
|
||||
};
|
||||
|
||||
|
||||
/** @private */
|
||||
ShakaControls.prototype.onTracksChange_ = function() {
|
||||
// TS content might have captions embedded in video stream, we can't know
|
||||
// until we start transmuxing. So, always show caption button if we're
|
||||
// playing TS content.
|
||||
if (ShakaDemoUtils.isTsContent(this.player_)) {
|
||||
this.captionButton_.style.display = 'inherit';
|
||||
} else {
|
||||
let hasText = this.player_.getTextTracks().length;
|
||||
this.captionButton_.style.display = hasText ? 'inherit' : 'none';
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/** @private */
|
||||
ShakaControls.prototype.onCaptionStateChange_ = function() {
|
||||
if (this.player_.isTextTrackVisible()) {
|
||||
this.captionButton_.style.color = 'white';
|
||||
} else {
|
||||
// Make the button look darker to show that the text track is inactive.
|
||||
this.captionButton_.style.color = 'rgba(255, 255, 255, 0.3)';
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/** @private */
|
||||
ShakaControls.prototype.onFullscreenClick_ = function() {
|
||||
if (!this.enabled_) return;
|
||||
|
||||
if (document.fullscreenElement) {
|
||||
document.exitFullscreen();
|
||||
} else {
|
||||
this.videoContainer_.requestFullscreen();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/** @private */
|
||||
ShakaControls.prototype.onCurrentTimeClick_ = function() {
|
||||
if (!this.enabled_) return;
|
||||
|
||||
// Jump to LIVE if the user clicks on the current time.
|
||||
if (this.player_.isLive()) {
|
||||
this.video_.currentTime = this.seekBar_.max;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Cycles trick play rate between -1, -2, -4, and -8.
|
||||
* @private
|
||||
*/
|
||||
ShakaControls.prototype.onRewindClick_ = function() {
|
||||
if (!this.enabled_) return;
|
||||
|
||||
if (!this.video_.duration) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.trickPlayRate_ = (this.trickPlayRate_ > 0 || this.trickPlayRate_ < -4) ?
|
||||
-1 : this.trickPlayRate_ * 2;
|
||||
this.player_.trickPlay(this.trickPlayRate_);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Cycles trick play rate between 1, 2, 4, and 8.
|
||||
* @private
|
||||
*/
|
||||
ShakaControls.prototype.onFastForwardClick_ = function() {
|
||||
if (!this.enabled_) return;
|
||||
|
||||
if (!this.video_.duration) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.trickPlayRate_ = (this.trickPlayRate_ < 0 || this.trickPlayRate_ > 4) ?
|
||||
1 : this.trickPlayRate_ * 2;
|
||||
this.player_.trickPlay(this.trickPlayRate_);
|
||||
};
|
||||
|
||||
|
||||
/** @private */
|
||||
ShakaControls.prototype.onCastClick_ = function() {
|
||||
if (!this.enabled_) return;
|
||||
|
||||
if (this.castProxy_.isCasting()) {
|
||||
this.castProxy_.suggestDisconnect();
|
||||
} else {
|
||||
this.castButton_.disabled = true;
|
||||
// Disable the load/unload buttons, to prevent the users from trying to load
|
||||
// an asset while the cast proxy is connecting.
|
||||
// That can lead to strange, erratic behavior.
|
||||
document.getElementById('loadButton').disabled = true;
|
||||
document.getElementById('unloadButton').disabled = true;
|
||||
this.castProxy_.cast().then(function() {
|
||||
document.getElementById('loadButton').disabled = false;
|
||||
document.getElementById('unloadButton').disabled = false;
|
||||
this.castButton_.disabled = false;
|
||||
// Success!
|
||||
}.bind(this), function(error) {
|
||||
this.castButton_.disabled = false;
|
||||
document.getElementById('loadButton').disabled = false;
|
||||
document.getElementById('unloadButton').disabled = false;
|
||||
if (error.code != shaka.util.Error.Code.CAST_CANCELED_BY_USER) {
|
||||
this.onError_(error);
|
||||
}
|
||||
}.bind(this));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {Event} event
|
||||
* @private
|
||||
*/
|
||||
ShakaControls.prototype.onCastStatusChange_ = function(event) {
|
||||
let canCast = this.castProxy_.canCast() && this.castAllowed_;
|
||||
let isCasting = this.castProxy_.isCasting();
|
||||
|
||||
this.notifyCastStatus_(isCasting);
|
||||
this.castButton_.style.display = canCast ? 'inherit' : 'none';
|
||||
this.castButton_.textContent = isCasting ? 'cast_connected' : 'cast';
|
||||
this.castReceiverName_.style.display =
|
||||
isCasting ? 'inherit' : 'none';
|
||||
this.castReceiverName_.textContent =
|
||||
isCasting ? 'Casting to ' + this.castProxy_.receiverName() : '';
|
||||
if (this.castProxy_.isCasting()) {
|
||||
this.controls_.classList.add('casting');
|
||||
} else {
|
||||
this.controls_.classList.remove('casting');
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {Event} event
|
||||
* @private
|
||||
*/
|
||||
ShakaControls.prototype.onBufferingStateChange_ = function(event) {
|
||||
this.bufferingSpinner_.style.display =
|
||||
event.buffering ? 'inherit' : 'none';
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {boolean} show True to show trick play controls, false to show seek
|
||||
* bar.
|
||||
*/
|
||||
ShakaControls.prototype.showTrickPlay = function(show) {
|
||||
this.seekBar_.parentElement.style.width = show ? 'auto' : '100%';
|
||||
this.seekBar_.style.display = show ? 'none' : 'flex';
|
||||
this.rewindButton_.style.display = show ? 'inline' : 'none';
|
||||
this.fastForwardButton_.style.display = show ? 'inline' : 'none';
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {boolean}
|
||||
* @private
|
||||
*/
|
||||
ShakaControls.prototype.isOpaque_ = function() {
|
||||
if (!this.enabled_) return false;
|
||||
|
||||
// While you are casting, the UI is always opaque.
|
||||
if (this.castProxy_ && this.castProxy_.isCasting()) return true;
|
||||
|
||||
let parentElement = this.controls_.parentElement;
|
||||
// The controls are opaque if either:
|
||||
// 1. We have explicitly made them so in JavaScript
|
||||
// 2. The browser has made them so via css and the hover state
|
||||
return (this.controls_.style.opacity == 1 ||
|
||||
parentElement.querySelector('#controls:hover') == this.controls_);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Called when the seek range or current time need to be updated.
|
||||
* @private
|
||||
*/
|
||||
ShakaControls.prototype.updateTimeAndSeekRange_ = function() {
|
||||
// Suppress updates if the controls are hidden.
|
||||
if (!this.isOpaque_()) {
|
||||
return;
|
||||
}
|
||||
|
||||
let displayTime = this.isSeeking_ ?
|
||||
this.seekBar_.value : this.video_.currentTime;
|
||||
let duration = this.video_.duration;
|
||||
let bufferedLength = this.video_.buffered.length;
|
||||
let bufferedStart = bufferedLength ? this.video_.buffered.start(0) : 0;
|
||||
let bufferedEnd =
|
||||
bufferedLength ? this.video_.buffered.end(bufferedLength - 1) : 0;
|
||||
let seekRange = this.player_.seekRange();
|
||||
let seekRangeSize = seekRange.end - seekRange.start;
|
||||
|
||||
this.seekBar_.min = seekRange.start;
|
||||
this.seekBar_.max = seekRange.end;
|
||||
|
||||
if (this.player_.isLive()) {
|
||||
// The amount of time we are behind the live edge.
|
||||
let behindLive = Math.floor(seekRange.end - displayTime);
|
||||
displayTime = Math.max(0, behindLive);
|
||||
|
||||
let showHour = seekRangeSize >= 3600;
|
||||
|
||||
// Consider "LIVE" when less than 1 second behind the live-edge. Always
|
||||
// show the full time string when seeking, including the leading '-';
|
||||
// otherwise, the time string "flickers" near the live-edge.
|
||||
if ((displayTime >= 1) || this.isSeeking_) {
|
||||
this.currentTime_.textContent =
|
||||
'- ' + this.buildTimeString_(displayTime, showHour);
|
||||
this.currentTime_.style.cursor = 'pointer';
|
||||
} else {
|
||||
this.currentTime_.textContent = 'LIVE';
|
||||
this.currentTime_.style.cursor = '';
|
||||
}
|
||||
|
||||
if (!this.isSeeking_) {
|
||||
this.seekBar_.value = seekRange.end - displayTime;
|
||||
}
|
||||
} else {
|
||||
let showHour = duration >= 3600;
|
||||
|
||||
this.currentTime_.textContent =
|
||||
this.buildTimeString_(displayTime, showHour);
|
||||
|
||||
if (!this.isSeeking_) {
|
||||
this.seekBar_.value = displayTime;
|
||||
}
|
||||
|
||||
this.currentTime_.style.cursor = '';
|
||||
}
|
||||
|
||||
let gradient = ['to right'];
|
||||
if (bufferedLength == 0) {
|
||||
gradient.push('#000 0%');
|
||||
} else {
|
||||
let clampedBufferStart = Math.max(bufferedStart, seekRange.start);
|
||||
let clampedBufferEnd = Math.min(bufferedEnd, seekRange.end);
|
||||
|
||||
let bufferStartDistance = clampedBufferStart - seekRange.start;
|
||||
let bufferEndDistance = clampedBufferEnd - seekRange.start;
|
||||
let playheadDistance = displayTime - seekRange.start;
|
||||
|
||||
// NOTE: the fallback to zero eliminates NaN.
|
||||
let bufferStartFraction = (bufferStartDistance / seekRangeSize) || 0;
|
||||
let bufferEndFraction = (bufferEndDistance / seekRangeSize) || 0;
|
||||
let playheadFraction = (playheadDistance / seekRangeSize) || 0;
|
||||
|
||||
gradient.push('#000 ' + (bufferStartFraction * 100) + '%');
|
||||
gradient.push('#ccc ' + (bufferStartFraction * 100) + '%');
|
||||
gradient.push('#ccc ' + (playheadFraction * 100) + '%');
|
||||
gradient.push('#444 ' + (playheadFraction * 100) + '%');
|
||||
gradient.push('#444 ' + (bufferEndFraction * 100) + '%');
|
||||
gradient.push('#000 ' + (bufferEndFraction * 100) + '%');
|
||||
}
|
||||
this.seekBar_.style.background =
|
||||
'linear-gradient(' + gradient.join(',') + ')';
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Builds a time string, e.g., 01:04:23, from |displayTime|.
|
||||
*
|
||||
* @param {number} displayTime
|
||||
* @param {boolean} showHour
|
||||
* @return {string}
|
||||
* @private
|
||||
*/
|
||||
ShakaControls.prototype.buildTimeString_ = function(displayTime, showHour) {
|
||||
let h = Math.floor(displayTime / 3600);
|
||||
let m = Math.floor((displayTime / 60) % 60);
|
||||
let s = Math.floor(displayTime % 60);
|
||||
if (s < 10) s = '0' + s;
|
||||
let text = m + ':' + s;
|
||||
if (showHour) {
|
||||
if (m < 10) text = '0' + text;
|
||||
text = h + ':' + text;
|
||||
}
|
||||
return text;
|
||||
};
|
||||
@@ -100,3 +100,138 @@ ShakaDemoUtils.isTsContent = function(player) {
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Creates a number of asset buttons, with selection functionality.
|
||||
* Clicking one of these elements will add the "selected" tag to it, and remove
|
||||
* the "selected" tag from the previously selected element.
|
||||
* @param {!Element} parentDiv The div to place the buttons in.
|
||||
* @param {!Array.<shakaAssets.AssetInfo>} assets The assets that should be
|
||||
* given buttons.
|
||||
* @param {?shakaAssets.AssetInfo} selectedAsset An asset that should start out
|
||||
* selected.
|
||||
* @param {function(!Element, shakaAssets.AssetInfo)} layout A function that is
|
||||
* called to lay out the contents of a button.
|
||||
* @param {function(shakaAssets.AssetInfo)} onclick A function that is called
|
||||
* when a button is clicked. This is after giving the button the "selected"
|
||||
* tag.
|
||||
*/
|
||||
ShakaDemoUtils.createAssetButtons = function(
|
||||
parentDiv, assets, selectedAsset, layout, onclick) {
|
||||
let assetButtons = [];
|
||||
for (let asset of assets) {
|
||||
let button = document.createElement('div');
|
||||
layout(button, asset);
|
||||
button.onclick = () => {
|
||||
onclick(asset);
|
||||
for (let button of assetButtons) {
|
||||
button.removeAttribute('selected');
|
||||
}
|
||||
button.setAttribute('selected', '');
|
||||
};
|
||||
parentDiv.appendChild(button);
|
||||
assetButtons.push(button);
|
||||
|
||||
if (asset == selectedAsset) {
|
||||
button.setAttribute('selected', '');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Goes through the various values in shaka.extern.PlayerConfiguration, and
|
||||
* calls the given callback on them so that they can be stored to or read from
|
||||
* an URL hash.
|
||||
* @param {function(string, string)} callback A callback to call on each config
|
||||
* value that can be automatically handled. The first parameter is the
|
||||
* hashName (desired name in the hash). The second parameter is the configName
|
||||
* (the full path of the value, as found in the config object).
|
||||
* @param {!shaka.extern.PlayerConfiguration} config A config object to use for
|
||||
* reference. Note that the exact config values in this are not used; it is
|
||||
* checked only to determine the shape and structure of a PlayerConfiguration
|
||||
* object.
|
||||
*/
|
||||
ShakaDemoUtils.runThroughHashParams = (callback, config) => {
|
||||
// Override the "natural" name for a config value in the hash.
|
||||
// This exists for legacy reasons; the previous demo page had some hash values
|
||||
// set to names that did not match the names of their corresonding config
|
||||
// object name.
|
||||
let overridden = [];
|
||||
let configOverride = (hashName, configName) => {
|
||||
overridden.push(configName);
|
||||
callback(hashName, configName);
|
||||
};
|
||||
|
||||
// Override config values with custom names.
|
||||
configOverride('audiolang', 'preferredAudioLanguage');
|
||||
configOverride('textlang', 'preferredTextLanguage');
|
||||
configOverride('channels', 'preferredAudioChannelCount');
|
||||
|
||||
// Override config values that are handled manually.
|
||||
overridden.push('abr.enabled');
|
||||
overridden.push('streaming.jumpLargeGaps');
|
||||
|
||||
// Determine which config values should be given full namespace names.
|
||||
// This is to remove ambiguity in situations where there are two objects in
|
||||
// the config that share a key with the same name, without wasting space by
|
||||
// pointlessly adding namespace information to every value.
|
||||
let added = [];
|
||||
let collisions = [];
|
||||
let findCollisions = (object) => {
|
||||
for (let key in object) {
|
||||
if (added.includes(key) && !collisions.includes(key)) {
|
||||
collisions.push(key);
|
||||
}
|
||||
added.push(key);
|
||||
|
||||
let value = object[key];
|
||||
if (typeof value != 'number' && typeof value != 'string' &&
|
||||
typeof value != 'boolean') {
|
||||
findCollisions(value);
|
||||
}
|
||||
}
|
||||
};
|
||||
findCollisions(config);
|
||||
|
||||
// TODO: This system for handling name collisions does mean that, if a new
|
||||
// collision appears later on, old hashes will become invalid.
|
||||
// E.g. if we add 'manifest.bufferBehind', then suddenly the page will
|
||||
// discard any 'bufferBehind=' values from old hashes.
|
||||
|
||||
// Now automatically do other config values.
|
||||
let handleConfig = (object, accumulated) => {
|
||||
for (let key in object) {
|
||||
let hashName = key;
|
||||
let configName = accumulated + key;
|
||||
if (overridden.includes(configName)) continue;
|
||||
if (collisions.includes(key)) {
|
||||
hashName = configName;
|
||||
}
|
||||
|
||||
let value = object[key];
|
||||
if (typeof value == 'number' || typeof value == 'string' ||
|
||||
typeof value == 'boolean') {
|
||||
callback(hashName, configName);
|
||||
} else {
|
||||
handleConfig(value, configName + '.');
|
||||
}
|
||||
}
|
||||
};
|
||||
handleConfig(config, '');
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {boolean} True if the browser would support the uncompiled build.
|
||||
*/
|
||||
ShakaDemoUtils.browserSupportsUncompiledMode = () => {
|
||||
// Check if ES6 arrow function syntax and ES7 async are usable. Both are
|
||||
// needed for uncompiled builds to work.
|
||||
try {
|
||||
eval('async ()=>{}');
|
||||
return true;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
|
||||
|
||||
/** @suppress {duplicate} */
|
||||
var shakaDemo = shakaDemo || {}; // eslint-disable-line no-var
|
||||
var shakaDemo = shakaDemo || {}; // eslint-disable-line no-var
|
||||
|
||||
|
||||
/** @private */
|
||||
@@ -37,12 +37,12 @@ shakaDemo.setupConfiguration_ = function() {
|
||||
'input', shakaDemo.onConfigInput_);
|
||||
document.getElementById('preferredTextLanguage').addEventListener(
|
||||
'input', shakaDemo.onConfigInput_);
|
||||
document.getElementById('preferredUILanguage').addEventListener(
|
||||
'input', shakaDemo.onConfigInput_);
|
||||
document.getElementById('preferredAudioChannelCount').addEventListener(
|
||||
'input', shakaDemo.onConfigInput_);
|
||||
document.getElementById('showNative').addEventListener(
|
||||
'change', shakaDemo.onNativeChange_);
|
||||
document.getElementById('showTrickPlay').addEventListener(
|
||||
'change', shakaDemo.onTrickPlayChange_);
|
||||
document.getElementById('enableAdaptation').addEventListener(
|
||||
'change', shakaDemo.onAdaptationChange_);
|
||||
document.getElementById('logLevelList').addEventListener(
|
||||
@@ -104,7 +104,7 @@ shakaDemo.onAvailabilityWindowOverrideChange_ = function(event) {
|
||||
*/
|
||||
shakaDemo.onLogLevelChange_ = function(event) {
|
||||
// shaka.log is not set if logging isn't enabled.
|
||||
// I.E. if using the release version of shaka.
|
||||
// I.E. if using the compiled version of shaka.
|
||||
if (shaka.log) {
|
||||
let logLevel = event.target[event.target.selectedIndex];
|
||||
switch (logLevel.value) {
|
||||
@@ -171,6 +171,11 @@ shakaDemo.onConfigInput_ = function(event) {
|
||||
document.getElementById('preferredTextLanguage').value,
|
||||
preferredAudioChannelCount: preferredAudioChannelCount,
|
||||
}));
|
||||
|
||||
const uiLang = document.getElementById('preferredUILanguage').value;
|
||||
shakaDemo.controls_.getLocalization().changeLocale([uiLang]);
|
||||
// TODO(#1591): Support multiple language preferences
|
||||
|
||||
// Change the hash, to mirror this.
|
||||
shakaDemo.hashShouldChange_();
|
||||
};
|
||||
@@ -195,16 +200,10 @@ shakaDemo.onAdaptationChange_ = function(event) {
|
||||
* @private
|
||||
*/
|
||||
shakaDemo.onNativeChange_ = function(event) {
|
||||
let showTrickPlay = document.getElementById('showTrickPlay');
|
||||
|
||||
if (event.target.checked) {
|
||||
showTrickPlay.checked = false;
|
||||
showTrickPlay.disabled = true;
|
||||
shakaDemo.controls_.showTrickPlay(false);
|
||||
shakaDemo.controls_.setEnabled(false);
|
||||
shakaDemo.controls_.setEnabledNativeControls(true);
|
||||
} else {
|
||||
showTrickPlay.disabled = false;
|
||||
shakaDemo.controls_.setEnabled(true);
|
||||
shakaDemo.controls_.setEnabledShakaControls(true);
|
||||
}
|
||||
|
||||
// Update text streaming config. When we use native controls, we must always
|
||||
@@ -218,15 +217,3 @@ shakaDemo.onNativeChange_ = function(event) {
|
||||
// Change the hash, to mirror this.
|
||||
shakaDemo.hashShouldChange_();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {!Event} event
|
||||
* @private
|
||||
*/
|
||||
shakaDemo.onTrickPlayChange_ = function(event) {
|
||||
// Show/hide trick play controls.
|
||||
shakaDemo.controls_.showTrickPlay(event.target.checked);
|
||||
// Change the hash, to mirror this.
|
||||
shakaDemo.hashShouldChange_();
|
||||
};
|
||||
|
||||
+6
-16
@@ -62,7 +62,7 @@ a.disabled_link {
|
||||
color: Gray;
|
||||
}
|
||||
|
||||
button {
|
||||
#loadButton, #unloadButton {
|
||||
background-color: #d04030;
|
||||
border: none;
|
||||
border-radius: 2px;
|
||||
@@ -75,11 +75,11 @@ button {
|
||||
width: 7.5em;
|
||||
}
|
||||
|
||||
button:active {
|
||||
#loadButton:active, #unloadButton:active {
|
||||
background-color: #e02020;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
#loadButton:hover, #unloadButton:hover {
|
||||
background-color: #e02020;
|
||||
}
|
||||
|
||||
@@ -327,16 +327,6 @@ body.noinput #logSection #log {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.overlay-parent {
|
||||
/* Makes this a positioned ancestor of .overlay */
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.overlay {
|
||||
/* Allows this to be positioned relative to a containing .overlay-parent */
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.for-screen-readers {
|
||||
/* Hide the content from sighted users, but not screen readers */
|
||||
position: absolute;
|
||||
@@ -358,12 +348,12 @@ body.noinput #logSection #log {
|
||||
}
|
||||
|
||||
@media screen and (max-width: 550px) {
|
||||
button {
|
||||
button.appButton {
|
||||
margin: 0 0.6em 0.8em 0;
|
||||
padding: 9px 0 10px 0;
|
||||
}
|
||||
|
||||
button:active {
|
||||
button.appButton:active {
|
||||
background-color: darkRed;
|
||||
}
|
||||
|
||||
@@ -392,7 +382,7 @@ body.noinput #logSection #log {
|
||||
}
|
||||
|
||||
@media screen and (max-width: 400px) {
|
||||
button {
|
||||
button.appButton {
|
||||
font-size: 0.8em;
|
||||
margin: 0 0.4em 1em 0;
|
||||
padding: 5px 0 7px 0;
|
||||
|
||||
+25
-48
@@ -29,8 +29,6 @@
|
||||
<link rel="manifest" href="app_manifest.json">
|
||||
<link rel="icon" href="favicon.ico">
|
||||
<link rel="stylesheet" href="demo.css">
|
||||
<link rel="stylesheet" href="common/controls.css">
|
||||
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto+Condensed">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
|
||||
@@ -41,16 +39,16 @@
|
||||
|
||||
<script>
|
||||
COMPILED_JS = [
|
||||
// The compiled library.
|
||||
'../dist/shaka-player.compiled.js',
|
||||
// The compiled library, with UI.
|
||||
'../dist/shaka-player.ui.js',
|
||||
// The compiled demo.
|
||||
'../dist/demo.compiled.js'
|
||||
'../dist/demo.compiled.js',
|
||||
];
|
||||
COMPILED_DEBUG_JS = [
|
||||
// The compiled library, debug mode.
|
||||
'../dist/shaka-player.compiled.debug.js',
|
||||
// The compiled library, with UI, debug mode.
|
||||
'../dist/shaka-player.ui.debug.js',
|
||||
// The compiled demo.
|
||||
'../dist/demo.compiled.debug.js'
|
||||
'../dist/demo.compiled.debug.js',
|
||||
];
|
||||
UNCOMPILED_JS = [
|
||||
// Bootstrap the Shaka Player library through the Closure library.
|
||||
@@ -58,18 +56,20 @@ UNCOMPILED_JS = [
|
||||
'../dist/deps.js',
|
||||
// This file contains goog.require calls for all exported library classes.
|
||||
'../shaka-player.uncompiled.js',
|
||||
// Enable less, the CSS pre-processor.
|
||||
'../node_modules/less/dist/less.js',
|
||||
// These are the individual parts of the demo app.
|
||||
'common/assets.js',
|
||||
'common/controls.js',
|
||||
'common/demo_utils.js',
|
||||
'main.js',
|
||||
'asset_section.js',
|
||||
'configuration_section.js',
|
||||
'info_section.js',
|
||||
'log_section.js',
|
||||
'offline_section.js'
|
||||
'offline_section.js',
|
||||
];
|
||||
</script>
|
||||
|
||||
</script>
|
||||
|
||||
<!-- Load the compiled or uncompiled version of the code. -->
|
||||
<script defer src="load.js"></script>
|
||||
@@ -80,9 +80,9 @@ UNCOMPILED_JS = [
|
||||
|
||||
<div class="input">
|
||||
<p id="compiled_links">
|
||||
<a href="#" id="uncompiled_link">uncompiled</a> |
|
||||
<a href="#" id="debug_compiled_link">compiled (debug)</a> |
|
||||
<a href="#" id="compiled_link">compiled (release)</a>
|
||||
<a target="_self" href="?build=uncompiled" id="uncompiled_link">uncompiled</a> |
|
||||
<a target="_self" href="?build=debug_compiled" id="debug_compiled_link">compiled (debug)</a> |
|
||||
<a target="_self" href="?build=compiled" id="compiled_link">compiled (release)</a>
|
||||
</p>
|
||||
<p>This is a demo of Google's Shaka Player, a JavaScript library for
|
||||
adaptive video streaming.</p>
|
||||
@@ -118,8 +118,8 @@ UNCOMPILED_JS = [
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<button id="loadButton">Load</button>
|
||||
<button id="unloadButton">Unload</button>
|
||||
<button id="loadButton" class="appButton">Load</button>
|
||||
<button id="unloadButton" class="appButton">Unload</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -127,32 +127,9 @@ UNCOMPILED_JS = [
|
||||
<div id="errorDisplayCloseButton">x</div>
|
||||
<a id="errorDisplayLink" href="#"></a>
|
||||
</div>
|
||||
<div id="videoContainer" class="overlay-parent">
|
||||
<video id="video" autoplay></video>
|
||||
<div id="giantPlayButtonContainer" class="overlay">
|
||||
<button id="giantPlayButton" class="material-icons">play_arrow</button>
|
||||
</div>
|
||||
<div id="bufferingSpinner" class="overlay">
|
||||
<svg class="spinnerSvg" viewBox="25 25 50 50">
|
||||
<circle class="spinnerPath" cx="50" cy="50" r="20"
|
||||
fill="none" stroke-width="2" stroke-miterlimit="10" />
|
||||
</svg>
|
||||
</div>
|
||||
<div id="castReceiverName" class="overlay"></div>
|
||||
<div id="controlsContainer" class="overlay"><div id="controls">
|
||||
<button id="playPauseButton" class="material-icons">play_arrow</button>
|
||||
<label for="seekBar" class="for-screen-readers">seek</label>
|
||||
<input id="seekBar" type="range" step="any" min="0" max="1" value="0">
|
||||
<button id="rewindButton" class="material-icons">fast_rewind</button>
|
||||
<div id="currentTime">0:00</div>
|
||||
<button id="fastForwardButton" class="material-icons">fast_forward</button>
|
||||
<button id="muteButton" class="material-icons">volume_up</button>
|
||||
<label for="volumeBar" class="for-screen-readers">volume</label>
|
||||
<input id="volumeBar" type="range" step="any" min="0" max="1" value="0">
|
||||
<button id="castButton" class="material-icons">cast</button>
|
||||
<button id="captionButton" class="material-icons">closed_caption</button>
|
||||
<button id="fullscreenButton" class="material-icons">fullscreen</button>
|
||||
</div></div>
|
||||
|
||||
<div data-shaka-player-container data-shaka-player-cast-receiver-id="7B25EC44">
|
||||
<video autoplay data-shaka-player id="video"></video>
|
||||
</div>
|
||||
|
||||
<details id="logSection">
|
||||
@@ -170,6 +147,10 @@ UNCOMPILED_JS = [
|
||||
<label for="preferredTextLanguage">Preferred text language:</label>
|
||||
<input id="preferredTextLanguage" class="flex-grow" type="text">
|
||||
</div>
|
||||
<div class="flex">
|
||||
<label for="preferredUILanguage">Preferred UI language:</label>
|
||||
<input id="preferredUILanguage" class="flex-grow" type="text">
|
||||
</div>
|
||||
<div class="flex">
|
||||
<label for="preferredAudioChannelCount">Preferred audio channel count:</label>
|
||||
<input id="preferredAudioChannelCount" type="number">
|
||||
@@ -178,10 +159,6 @@ UNCOMPILED_JS = [
|
||||
<label for="showNative">Show native browser controls:</label>
|
||||
<input id="showNative" type="checkbox">
|
||||
</div>
|
||||
<div>
|
||||
<label for="showTrickPlay">Show trick play controls:</label>
|
||||
<input id="showTrickPlay" type="checkbox">
|
||||
</div>
|
||||
<div>
|
||||
<label for="enableAdaptation">Enable adaptation:</label>
|
||||
<input id="enableAdaptation" type="checkbox" checked>
|
||||
@@ -223,7 +200,7 @@ UNCOMPILED_JS = [
|
||||
<label for="availabilityWindowOverride">Availability Window Override:</label>
|
||||
<input id="availabilityWindowOverride" type="number" step="30" min="0">
|
||||
</div>
|
||||
<datalist id="robustnessSuggestions"></datalist>
|
||||
<datalist id="robustnessSuggestions"></datalist>
|
||||
</details>
|
||||
|
||||
<details class="input">
|
||||
@@ -262,7 +239,7 @@ UNCOMPILED_JS = [
|
||||
<details id="offlineSection" class="input">
|
||||
<summary>Offline</summary>
|
||||
<div>
|
||||
<button id="storeDeleteButton">Store</button>
|
||||
<button id="storeDeleteButton" class="appButton">Store</button>
|
||||
<span id="storeDeleteHelpText"></span>
|
||||
</div>
|
||||
<div id="progressDiv">
|
||||
|
||||
+45
-1
@@ -47,6 +47,22 @@ function shakaUncompiledModeSupported() {
|
||||
importScript(baseUrl + src);
|
||||
}
|
||||
|
||||
// NOTE: This is a quick-and-easy hack based on assumption that the old
|
||||
// demo page will be replaced in the near future.
|
||||
function loadCss(buildType) {
|
||||
var link = document.createElement('link');
|
||||
link.type = 'text/css';
|
||||
if (buildType == 'uncompiled') {
|
||||
link.rel = 'stylesheet/less';
|
||||
link.href = baseUrl + '../ui/controls.less';
|
||||
} else {
|
||||
link.rel = 'stylesheet';
|
||||
link.href = baseUrl + '../dist/controls.css';
|
||||
}
|
||||
|
||||
document.head.appendChild(link);
|
||||
}
|
||||
|
||||
function importScript(src) {
|
||||
var script = document.createElement('script');
|
||||
script.type = 'text/javascript';
|
||||
@@ -57,6 +73,7 @@ function shakaUncompiledModeSupported() {
|
||||
script.async = false;
|
||||
document.head.appendChild(script);
|
||||
}
|
||||
|
||||
window.CLOSURE_IMPORT_SCRIPT = importScript;
|
||||
|
||||
var fields = location.search.substr(1);
|
||||
@@ -85,7 +102,7 @@ function shakaUncompiledModeSupported() {
|
||||
|
||||
// Very old browsers do not have Array.prototype.indexOf, so we loop.
|
||||
for (var i = 0; i < combined.length; ++i) {
|
||||
if (combined[i] == 'compiled' || combined[i] == 'build=compiled') {
|
||||
if (combined[i] == 'build=compiled') {
|
||||
scripts = window['COMPILED_JS'];
|
||||
buildType = 'compiled';
|
||||
buildSpecified = true;
|
||||
@@ -97,6 +114,31 @@ function shakaUncompiledModeSupported() {
|
||||
buildSpecified = true;
|
||||
break;
|
||||
}
|
||||
if (combined[i] == 'build=uncompiled') {
|
||||
scripts = window['UNCOMPILED_JS'];
|
||||
buildType = 'uncompiled';
|
||||
buildSpecified = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!shakaUncompiledModeSupported() && buildType == 'uncompiled') {
|
||||
// The URL says uncompiled, but we know it won't work. This URL was
|
||||
// probably copied from some other browser, but it won't work in this one.
|
||||
// Force the use of the compiled debug build and update the hash.
|
||||
scripts = window['COMPILED_DEBUG_JS'];
|
||||
buildType = 'debug_compiled';
|
||||
|
||||
// Replace the build type in either the hash or the URL parameters.
|
||||
// At this point, we don't know precisely which contained the build type.
|
||||
location.hash = location.hash.replace(
|
||||
'build=uncompiled', 'build=debug_compiled');
|
||||
location.href = location.href.replace(
|
||||
'build=uncompiled', 'build=debug_compiled');
|
||||
// Changing location.href will trigger a refresh of the page. If the
|
||||
// build type was in the URL parameters, the page will now be refreshed.
|
||||
// If the build type was in the hash, the page will continue to load with
|
||||
// an updated hash and the correct library type.
|
||||
}
|
||||
|
||||
// If no build was specified in the URL, update the fragment with the default
|
||||
@@ -108,6 +150,8 @@ function shakaUncompiledModeSupported() {
|
||||
location.hash += 'build=' + buildType;
|
||||
}
|
||||
|
||||
loadCss(buildType);
|
||||
|
||||
// The application must define its list of compiled and uncompiled sources
|
||||
// before including this loader. The URLs should be relative to the page.
|
||||
for (var j = 0; j < scripts.length; ++j) {
|
||||
|
||||
+50
-63
@@ -51,7 +51,7 @@ shakaDemo.localPlayer_ = null;
|
||||
shakaDemo.support_;
|
||||
|
||||
|
||||
/** @private {ShakaControls} */
|
||||
/** @private {shaka.ui.Controls} */
|
||||
shakaDemo.controls_ = null;
|
||||
|
||||
|
||||
@@ -83,14 +83,6 @@ shakaDemo.audioOnlyPoster_ =
|
||||
'https://shaka-player-demo.appspot.com/assets/audioOnly.gif';
|
||||
|
||||
|
||||
/**
|
||||
* The registered ID of the v2.4 Chromecast receiver demo.
|
||||
* @const {string}
|
||||
* @private
|
||||
*/
|
||||
shakaDemo.CC_APP_ID_ = '7B25EC44';
|
||||
|
||||
|
||||
/**
|
||||
* Initialize the application.
|
||||
*/
|
||||
@@ -102,9 +94,11 @@ shakaDemo.init = function() {
|
||||
document.getElementById('version').textContent = shaka.Player.version;
|
||||
|
||||
// Fill in the language preferences based on browser config, if available.
|
||||
let language = navigator.language || 'en-us';
|
||||
document.getElementById('preferredAudioLanguage').value = language;
|
||||
document.getElementById('preferredTextLanguage').value = language;
|
||||
const languages = navigator.languages || ['en-us'];
|
||||
document.getElementById('preferredAudioLanguage').value = languages[0];
|
||||
document.getElementById('preferredTextLanguage').value = languages[0];
|
||||
document.getElementById('preferredUILanguage').value = languages[0];
|
||||
// TODO(#1591): Support multiple language preferences
|
||||
|
||||
document.getElementById('preferredAudioChannelCount').value = '2';
|
||||
|
||||
@@ -114,8 +108,6 @@ shakaDemo.init = function() {
|
||||
|
||||
shakaDemo.preBrowserCheckParams_(params);
|
||||
|
||||
shaka.polyfill.installAll();
|
||||
|
||||
// Display uncaught exceptions.
|
||||
window.addEventListener('error', function(event) {
|
||||
shakaDemo.onError_(/** @type {!shaka.util.Error} */ (event.error));
|
||||
@@ -187,9 +179,12 @@ shakaDemo.init = function() {
|
||||
|
||||
let localVideo =
|
||||
/** @type {!HTMLVideoElement} */(document.getElementById('video'));
|
||||
let localPlayer = new shaka.Player(localVideo);
|
||||
shakaDemo.castProxy_ = new shaka.cast.CastProxy(
|
||||
localVideo, localPlayer, shakaDemo.CC_APP_ID_);
|
||||
|
||||
let videoContainer = localVideo.parentElement;
|
||||
let ui = localVideo['ui'];
|
||||
|
||||
let localPlayer = ui.getPlayer();
|
||||
shakaDemo.castProxy_ = ui.getControls().getCastProxy();
|
||||
|
||||
shakaDemo.video_ = shakaDemo.castProxy_.getVideo();
|
||||
shakaDemo.player_ = shakaDemo.castProxy_.getPlayer();
|
||||
@@ -202,12 +197,30 @@ shakaDemo.init = function() {
|
||||
|
||||
let asyncSetup = shakaDemo.setupAssets_();
|
||||
shakaDemo.setupOffline_();
|
||||
shakaDemo.setupConfiguration_();
|
||||
shakaDemo.setupInfo_();
|
||||
shakaDemo.setupConfiguration_();
|
||||
|
||||
shakaDemo.controls_ = new ShakaControls();
|
||||
shakaDemo.controls_.init(shakaDemo.castProxy_, shakaDemo.onError_,
|
||||
shakaDemo.onCastStatusChange_);
|
||||
// Workarond for goog.asserts not working yet
|
||||
if (videoContainer == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
shakaDemo.controls_ = ui.getControls();
|
||||
|
||||
const uiLang = document.getElementById('preferredUILanguage').value;
|
||||
shakaDemo.controls_.getLocalization().changeLocale([uiLang]);
|
||||
// TODO(#1591): Support multiple language preferences
|
||||
|
||||
shakaDemo.controls_.addEventListener('error', (event) => {
|
||||
shakaDemo.onError_(event['errorDetails']);
|
||||
});
|
||||
|
||||
shakaDemo.controls_.addEventListener('caststatuschanged', (event) => {
|
||||
shakaDemo.onCastStatusChange_(event['newStatus']);
|
||||
});
|
||||
|
||||
// Disable controls until something is loaded
|
||||
shakaDemo.controls_.setEnabledShakaControls(false);
|
||||
|
||||
asyncSetup.catch(function(error) {
|
||||
// shakaDemo.setupOfflineAssets_ errored while trying to
|
||||
@@ -267,6 +280,7 @@ shakaDemo.preBrowserCheckParams_ = function(params) {
|
||||
if ('lang' in params) {
|
||||
document.getElementById('preferredAudioLanguage').value = params['lang'];
|
||||
document.getElementById('preferredTextLanguage').value = params['lang'];
|
||||
document.getElementById('preferredUILanguage').value = params['lang'];
|
||||
}
|
||||
if ('audiolang' in params) {
|
||||
document.getElementById('preferredAudioLanguage').value =
|
||||
@@ -275,6 +289,9 @@ shakaDemo.preBrowserCheckParams_ = function(params) {
|
||||
if ('textlang' in params) {
|
||||
document.getElementById('preferredTextLanguage').value = params['textlang'];
|
||||
}
|
||||
if ('uilang' in params) {
|
||||
document.getElementById('preferredUILanguage').value = params['uilang'];
|
||||
}
|
||||
if ('channels' in params) {
|
||||
document.getElementById('preferredAudioChannelCount').value =
|
||||
params['channels'];
|
||||
@@ -445,15 +462,6 @@ shakaDemo.postBrowserCheckParams_ = function(params) {
|
||||
shakaDemo.onAdaptationChange_(fakeEvent);
|
||||
}
|
||||
|
||||
if ('trickplay' in params) {
|
||||
let showTrickPlay = document.getElementById('showTrickPlay');
|
||||
showTrickPlay.checked = true;
|
||||
// Call onTrickPlayChange_ manually, because setting checked
|
||||
// programatically doesn't fire a 'change' event.
|
||||
let fakeEvent = /** @type {!Event} */({target: showTrickPlay});
|
||||
shakaDemo.onTrickPlayChange_(fakeEvent);
|
||||
}
|
||||
|
||||
if ('nativecontrols' in params) {
|
||||
let showNative = document.getElementById('showNative');
|
||||
showNative.checked = true;
|
||||
@@ -561,13 +569,15 @@ shakaDemo.hashShouldChange_ = function() {
|
||||
if (document.getElementById('jumpLargeGaps').checked) {
|
||||
params.push('jumpLargeGaps');
|
||||
}
|
||||
let audioLang = document.getElementById('preferredAudioLanguage').value;
|
||||
let textLang = document.getElementById('preferredTextLanguage').value;
|
||||
if (textLang != audioLang) {
|
||||
const audioLang = document.getElementById('preferredAudioLanguage').value;
|
||||
const textLang = document.getElementById('preferredTextLanguage').value;
|
||||
const uiLang = document.getElementById('preferredUILanguage').value;
|
||||
if (textLang == audioLang && audioLang == uiLang) {
|
||||
params.push('lang=' + audioLang);
|
||||
} else {
|
||||
params.push('audiolang=' + audioLang);
|
||||
params.push('textlang=' + textLang);
|
||||
} else {
|
||||
params.push('lang=' + audioLang);
|
||||
params.push('uilang=' + uiLang);
|
||||
}
|
||||
let channels = document.getElementById('preferredAudioChannelCount').value;
|
||||
if (channels != '2') {
|
||||
@@ -579,9 +589,6 @@ shakaDemo.hashShouldChange_ = function() {
|
||||
if (!document.getElementById('enableAdaptation').checked) {
|
||||
params.push('noadaptation');
|
||||
}
|
||||
if (document.getElementById('showTrickPlay').checked) {
|
||||
params.push('trickplay');
|
||||
}
|
||||
if (document.getElementById('showNative').checked) {
|
||||
params.push('nativecontrols');
|
||||
}
|
||||
@@ -615,6 +622,7 @@ shakaDemo.hashShouldChange_ = function() {
|
||||
if (videoRobustness) {
|
||||
params.push('videoRobustness=' + videoRobustness);
|
||||
}
|
||||
|
||||
let audioRobustness =
|
||||
document.getElementById('drmSettingsAudioRobustness').value;
|
||||
if (audioRobustness) {
|
||||
@@ -634,8 +642,11 @@ shakaDemo.hashShouldChange_ = function() {
|
||||
buildType = 'compiled';
|
||||
}
|
||||
|
||||
// Make the build links smart enough to preserve the app state while changing
|
||||
// the build type.
|
||||
(['compiled', 'debug_compiled', 'uncompiled']).forEach(function(type) {
|
||||
let elem = document.getElementById(type + '_link');
|
||||
elem.href = '#';
|
||||
if (buildType == type) {
|
||||
elem.classList.add('disabled_link');
|
||||
elem.removeAttribute('href');
|
||||
@@ -739,28 +750,4 @@ shakaDemo.closeError = function() {
|
||||
};
|
||||
|
||||
|
||||
// IE 9 fires DOMContentLoaded, and enters the "interactive" readyState, before
|
||||
// document.body has been initialized, so wait for window.load.
|
||||
if (document.readyState == 'loading' ||
|
||||
document.readyState == 'interactive') {
|
||||
if (window.attachEvent) {
|
||||
// IE8
|
||||
window.attachEvent('onload', shakaDemo.init);
|
||||
} else {
|
||||
window.addEventListener('load', shakaDemo.init);
|
||||
}
|
||||
} else {
|
||||
/**
|
||||
* Poll for Shaka Player on window. On IE 11, the document is "ready", but
|
||||
* there are still deferred scripts being loaded. This does not occur on
|
||||
* Chrome or Edge, which set the document's state at the correct time.
|
||||
*/
|
||||
let pollForShakaPlayer = function() {
|
||||
if (window.shaka) {
|
||||
shakaDemo.init();
|
||||
} else {
|
||||
setTimeout(pollForShakaPlayer, 100);
|
||||
}
|
||||
};
|
||||
pollForShakaPlayer();
|
||||
}
|
||||
document.addEventListener('shaka-ui-loaded', shakaDemo.init);
|
||||
|
||||
@@ -72,7 +72,7 @@ const CRITICAL_RESOURCES = [
|
||||
'https://fonts.googleapis.com/icon?family=Material+Icons',
|
||||
|
||||
'load.js',
|
||||
'../dist/shaka-player.compiled.js',
|
||||
'../dist/shaka-player.ui.js',
|
||||
'../dist/demo.compiled.js',
|
||||
];
|
||||
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
# Shaka Player - Fuzzy Locale Matching
|
||||
|
||||
## Summary
|
||||
Language is a complex issue, and trying to match what language someone speaks with the best content option leaves a lot of room for interpretation. The core lesson we have learned from this is that if you try to be strict, you will be wrong most of the time. Because of this, when comparing the languages that a user wants with the content we have, we have adopted this strategy.
|
||||
|
||||
## Background
|
||||
For a full explanation of the terms and definitions used when we talk about language please see our ["Talking about Languages"](talking-about-languages.md) article.
|
||||
|
||||
## Strategy
|
||||
The question boils down to, "If the user says they prefer a specific locale, and we don't have it. What locale should we present to them?" To make things easier on us, we do not support dialects in our searches. When we read the locale, we purposely drop the dialect component as it simplifies the searches. We chose to adopt this short-cut because we have yet to see dialects appear in any content.
|
||||
|
||||
Conceptually locales follow a tree-like structure, so give a set of locales, we could create a tree like:
|
||||
|
||||

|
||||
|
||||
When we look for the best match, we try three searches. The searches are ordered from best to worst match so that once a match is found, we can stop searching. The searches are:
|
||||
- Locale Compatible
|
||||
- Parent Locale - A check where we see if one of our locales is the "parent" of the user's locale. For this to work:
|
||||
- our locale must only have a language component
|
||||
- the user's locale must have a language and region component
|
||||
- both locales must be Language Compatible
|
||||
- Language Compatible
|
||||
|
||||
## Examples
|
||||
If we assume that we can only respond to requests with the following locales:
|
||||
- en
|
||||
- en-CA
|
||||
- en-US
|
||||
- fr-CA
|
||||
|
||||
Then for any given request, we should be able to identify what match we can make and what locale we would respond wit. The table below shows some examples:
|
||||
|
||||
```
|
||||
User Wants | Matching Type | Locale
|
||||
-----------|---------------------|----------
|
||||
en | Locale Compatible | en
|
||||
en-UK | Parent Locale | en
|
||||
fr | Language Compatible | fr-CA
|
||||
zk | No Match | No Match
|
||||
```
|
||||
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 78 KiB |
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 17 KiB |
@@ -0,0 +1,46 @@
|
||||
# Shaka Player - Talking About Language
|
||||
|
||||
## Summary
|
||||
This document outlines the standard set of terms and definitions used by Shaka Player when working with language.
|
||||
|
||||
## Locales
|
||||
A __locale__ is the top level "object" and is composed of three __components__:
|
||||
- __language__, a lowercase 2-character code. Preferably from [ISO 639](http://www.loc.gov/standards/iso639-2/php/code_list.php).
|
||||
- __region__, an uppercase 2-character code. Preferably from ISO 3166.
|
||||
- __dialect__, a lowercase n-character code.
|
||||
|
||||
A locale is not required to use every component but must follow one of three patterns:
|
||||
- language (e.g. "en")
|
||||
- language-REGION (e.g. "en-US")
|
||||
- language-REGION-dialect (e.g. "en-US-wa")
|
||||
|
||||
## Relationships
|
||||
Locales follow a tree-like structure, so we use tree-like terms to talk about the relationships between locales:
|
||||
|
||||

|
||||
|
||||
- "en" is the parent of "en-US".
|
||||
- "en-US" is the parent of "en-US-tx".
|
||||
- "en" is the grandparent of "en-US-tx".
|
||||
- "en-US" and "en-CA" are siblings.
|
||||
- "en-US-tx" and "en-US-wa" are siblings.
|
||||
|
||||
## Compatibility
|
||||
There are three types of compatibility:
|
||||
- Locale Compatible - When two locales share the same language, region, and dialect.
|
||||
- "en" and "en"
|
||||
- "en-US" and "en-US"
|
||||
- "en-US-wa" and "en-US-wa"
|
||||
- Region Compatible - When two locales share the same language and region.
|
||||
- "en-US" and "en-US"
|
||||
- "en-US" and "en-US-wa"
|
||||
- "en-US-wa" and "en-US-tx"
|
||||
- Language Compatible - When two locales share the same language.
|
||||
- "en-US" and "en-US"
|
||||
- "en-US" and "en-CA"
|
||||
- "en-US-wa" and "en-US-tx"
|
||||
- "en-US-wa" and "en-CA-mb"
|
||||
- "en" and "en-US-wa"
|
||||
- "en-US" and "en-US-wa"
|
||||
|
||||

|
||||
@@ -0,0 +1,31 @@
|
||||
# Shaka UI Library Design
|
||||
|
||||
## Overview
|
||||
|
||||
Shaka UI is a customizable, easy to set up UI layer for Shaka Player with built-in
|
||||
accessibility and localization support. It provides a default set of video controls
|
||||
that are visually similar to Chrome native controls and can be added to an application
|
||||
with just one line of code and styled through CSS. Using the UI API, the UI can be
|
||||
customized to better meet applications' needs.
|
||||
|
||||
UI layer is a part of the default Shaka build, but can be excluded from the build by
|
||||
applications.
|
||||
|
||||
|
||||
## Player-UI relationships
|
||||
|
||||
Arrows signify ownership (`shaka.UI.Overlay` -> `shaka.Player` mean that
|
||||
`shaka.UI.Overlay` class has a `shaka.Player` field).
|
||||
|
||||

|
||||
|
||||
The following statements are true:
|
||||
```js
|
||||
container.ui == video.ui
|
||||
castProxy.video == video
|
||||
castProxy.player == ui.player
|
||||
```
|
||||
|
||||
For details on adding Shaka UI to your application, see [UI library tutorial].
|
||||
|
||||
[UI library tutorial]: https://uipreview-dot-shaka-player-demo.appspot.com/docs/api/tutorial-ui.html
|
||||
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 61 KiB |
@@ -5,6 +5,7 @@
|
||||
"source": {
|
||||
"include": [
|
||||
"lib",
|
||||
"ui",
|
||||
"externs/shaka"
|
||||
]
|
||||
},
|
||||
|
||||
@@ -191,4 +191,4 @@ To sum up, remember these points when debugging your application:
|
||||
|
||||
#### Continue the Tutorials
|
||||
|
||||
Next, check out {@tutorial config}.
|
||||
Next, check out {@tutorial ui}.
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
{ "welcome": { "title": "Welcome to Shaka Player" } },
|
||||
{ "basic-usage": { "title": "Basic Usage" } },
|
||||
{ "debugging": { "title": "Debugging" } },
|
||||
{ "ui": { "title": "UI Library" } },
|
||||
{ "config": { "title": "Configuration" } },
|
||||
{ "network-and-buffering-config": { "title": "Network and Buffering Configuration" } },
|
||||
{ "drm-config": { "title": "DRM Configuration" } },
|
||||
|
||||
@@ -0,0 +1,278 @@
|
||||
# UI Library
|
||||
|
||||
Shaka Player has an optional UI library that provides a high-quality accessible
|
||||
localized UI. It is an alternate bundle from the base
|
||||
Shaka Player library, that adds additional UI-specific classes and a streamlined
|
||||
declarative style of setup.
|
||||
|
||||
#### Setting up the UI library
|
||||
|
||||
Setting up a project with the UI library is even easier than setting one up without.
|
||||
|
||||
Option 1: Set up controls with HTML data attributes:
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<!-- Shaka Player ui compiled library: -->
|
||||
<script src="dist/shaka-player.ui.js"></script>
|
||||
<!-- Shaka Player ui compiled library default CSS: -->
|
||||
<link rel="stylesheet" type="text/css" href="dist/controls.css">
|
||||
<!-- Google Material Design Icons: -->
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
|
||||
<!-- Your application source: -->
|
||||
<script src="myapp.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<!-- The data-shaka-player-container tag will make the UI library place the controls in this div.
|
||||
The data-shaka-player-cast-receiver-id tag allows you to provide a Cast Application ID that
|
||||
the cast button will cast to; the value provided here is the sample cast receiver. -->
|
||||
<div data-shaka-player-container style="max-width:40em"
|
||||
data-shaka-player-cast-receiver-id="A15A181D">
|
||||
<!-- The data-shaka-player tag will make the UI library use this video element.
|
||||
If no video is provided, the UI will automatically make one inside the container div. -->
|
||||
<video autoplay data-shaka-player id="video" style="width:100%;height:100%"></video>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
```js
|
||||
// myapp.js
|
||||
|
||||
var manifestUri =
|
||||
'https://storage.googleapis.com/shaka-demo-assets/angel-one/dash.mpd';
|
||||
|
||||
async function init() {
|
||||
// When using the UI, the player is made automatically by the UI object.
|
||||
const video = document.getElementById('video');
|
||||
const ui = video['ui'];
|
||||
const player = ui.getPlayer();
|
||||
const controls = ui.getControls();
|
||||
|
||||
// Listen for error events.
|
||||
player.addEventListener('error', onPlayerErrorEvent);
|
||||
controls.addEventListener('error', onUIErrorEvent);
|
||||
|
||||
// Try to load a manifest.
|
||||
// This is an asynchronous process.
|
||||
try {
|
||||
await player.load(manifestUri);
|
||||
// This runs if the asynchronous load is successful.
|
||||
console.log('The video has now been loaded!');
|
||||
} catch (error) {
|
||||
onError(error);
|
||||
}
|
||||
}
|
||||
|
||||
function onPlayerErrorEvent(errorEvent) {
|
||||
// Extract the shaka.util.Error object from the event.
|
||||
onPlayerError(event.detail);
|
||||
}
|
||||
|
||||
function onPlayerError(error) {
|
||||
// Handle player error
|
||||
}
|
||||
|
||||
function onUIErrorEvent(errorEvent) {
|
||||
// Handle UI error
|
||||
}
|
||||
|
||||
// Listen to the custom shaka-ui-loaded event, to wait until the UI is loaded.
|
||||
document.addEventListener('shaka-ui-loaded', init);
|
||||
|
||||
```
|
||||
|
||||
#### Enabling Chromecast support
|
||||
|
||||
If you'd like to take advantage of Shaka's built-in Chromecast support,
|
||||
you will need to provide us with your cast receiver application id.
|
||||
If you want to track cast status changes, you should also
|
||||
set up a listener for the 'caststatuschanged' events.
|
||||
|
||||
```html
|
||||
<!-- Add a data-shaka-player-cast-receiver-id tag to provide a Cast Application ID that
|
||||
the cast button will cast to; the value provided here is the sample cast receiver. -->
|
||||
<div data-shaka-player-container style="max-width:40em"
|
||||
data-shaka-player-cast-receiver-id="A15A181D">
|
||||
</div>
|
||||
|
||||
```
|
||||
|
||||
With the UI library set up this way, it will provide a button for casting to a
|
||||
ChromeCast when appropriate, without any extra code.
|
||||
Next, let's add a listener to the 'caststatuschanged' event in myapp.js:
|
||||
|
||||
|
||||
```js
|
||||
controls.addEventListener('caststatuschanged', onCastStatusChanged);
|
||||
|
||||
function onCastStatusChanged(event) {
|
||||
const newCastStatus = event['newStatus'];
|
||||
// Handle cast status change
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
<!-- TODO: Also mention the download button, once we add it. -->
|
||||
#### Option 2: Setting the UI element programmatically
|
||||
|
||||
The most basic way to make an element host the Shaka Player UI is to use the
|
||||
data-shaka-player tags in the HTML.
|
||||
However, that approach won't necessarily work for every application. For
|
||||
instance, if your site lays itself out programmatically, that is not an option.
|
||||
In addition, when using data-shaka-player tags, advanced customization options
|
||||
are not available.
|
||||
For more advanced use, the UI can be assigned to an element in code.
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<!-- Shaka Player ui compiled library: -->
|
||||
<script src="dist/shaka-player.ui.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="dist/controls.css">
|
||||
<!-- Google Material Design Icons: -->
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
|
||||
<!-- Your application source: -->
|
||||
<script src="myapp.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="videoContainer" style="max-width:40em">
|
||||
<video autoplay id="video" style="width:100%;height:100%"></video>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
```js
|
||||
// myapp.js
|
||||
|
||||
var manifestUri =
|
||||
'https://storage.googleapis.com/shaka-demo-assets/angel-one/dash.mpd';
|
||||
|
||||
async function init() {
|
||||
// Create the UI manually.
|
||||
const video = document.getElementById('video');
|
||||
const videoContainer = document.getElementById('videoContainer');
|
||||
const player = new shaka.Player(video);
|
||||
// Use this to pass in desired config values. Config values not passed in
|
||||
// will be filled out according to the default config.
|
||||
// See more info on the configuration in the section below.
|
||||
const uiConfig = {};
|
||||
const ui = new shaka.ui.Overlay(player, videoContainer, video, uiConfig);
|
||||
const controls = ui.getControls();
|
||||
|
||||
// Listen for error events.
|
||||
player.addEventListener('error', onPlayerErrorEvent);
|
||||
controls.addEventListener('error', onUIErrorEvent);
|
||||
controls.addEventListener('caststatuschanged', onCastStatusChanged);
|
||||
|
||||
// Try to load a manifest.
|
||||
// This is an asynchronous process.
|
||||
try {
|
||||
await player.load(manifestUri);
|
||||
// This runs if the asynchronous load is successful.
|
||||
console.log('The video has now been loaded!');
|
||||
} catch (error) {
|
||||
onError(error);
|
||||
}
|
||||
}
|
||||
|
||||
function onPlayerErrorEvent(errorEvent) {
|
||||
// Extract the shaka.util.Error object from the event.
|
||||
onPlayerError(event.detail);
|
||||
}
|
||||
|
||||
function onPlayerError(error) {
|
||||
// Handle player error
|
||||
}
|
||||
|
||||
function onUIErrorEvent(errorEvent) {
|
||||
// Handle UI error
|
||||
}
|
||||
|
||||
function onCastStatusChanged(event) {
|
||||
// Handle cast status change
|
||||
}
|
||||
|
||||
// The shaka-ui-loaded event won't fire if there are no tagged UI elements to
|
||||
// set up, so listen to DOMContentLoaded instead.
|
||||
document.addEventListener('DOMContentLoaded', init);
|
||||
|
||||
```
|
||||
#### Configuring the UI
|
||||
|
||||
When creating the UI via code, you can pass in configuration options that change
|
||||
the look and functioning of the UI bar. For example, if you wanted to not have
|
||||
a seek bar, you could add the following line to initPlayer, right before
|
||||
creating the UI overlay:
|
||||
|
||||
```js
|
||||
config['addSeekBar'] = false;
|
||||
```
|
||||
|
||||
See the docs on [UIConfiguration][UIConfiguration] for more information.
|
||||
|
||||
#### Customizing the number and order of controls
|
||||
|
||||
For example, let's say that all you care about for your app is rewinding and
|
||||
fast-forwarding. You could add the following line to init(), right before
|
||||
creating the UI overlay. This will configure UI to ONLY provide these two buttons:
|
||||
|
||||
```js
|
||||
config['controlPanelElements'] = ['rewind', 'fast_forward'];
|
||||
```
|
||||
This call will result in the controls panel having only two elements: rewind
|
||||
button and fast forward button, in that order. If the reversed order is desired,
|
||||
the call should be:
|
||||
|
||||
```js
|
||||
config['controlPanelElements'] = ['fast_forward', 'rewind'];
|
||||
```
|
||||
The following elements can be added to the UI bar using this configuration value:
|
||||
* time_and_duration: adds an element tracking and displaying current progress of
|
||||
the presentation and the full presentation duration in the "0:10 / 1:00"
|
||||
form where "0:10" (ten seconds) is the number of seconds passed from the start of the presentation
|
||||
and "1:00" (one minute) is the presentation duration.
|
||||
* mute: adds a button that mutes/unmutes the video on click.
|
||||
* volume: adds a volume slider.
|
||||
* fullscreen: adds a button that toggles full screen mode on click.
|
||||
* overflow_menu: adds a button that opens an overflow menu with additional settings
|
||||
buttons. It's content is also configurable.
|
||||
* rewind: adds a button that rewinds the presentation on click; that is, it starts playing
|
||||
the presentation backwards.
|
||||
* fast_forward: adds a button that fast forwards the presentation on click; that is, it
|
||||
starts playing the presentation at an increased speed
|
||||
<!-- TODO: If we add more buttons that can be put in the order this way, list them here. -->
|
||||
At most one button of each type can be added at a time.
|
||||
|
||||
Similarly, the 'overflowMenuButtons' configuration option can be used to control
|
||||
the contents of the overflow menu.
|
||||
The following buttons can be added to the overflow menu:
|
||||
* captions: adds a button that controls the current text track selection (including turning it off).
|
||||
The button is visible only if the content has at least one text track.
|
||||
* cast: adds a button that opens a Chromecast dialog. The button is visible only if there is
|
||||
at least one Chromecast device on the same network available for casting.
|
||||
* quality: adds a button that controls enabling/disabling of abr and video resolution selection.
|
||||
* language: adds a button that controls audio language selection.
|
||||
|
||||
Please note that custom layouts might need CSS adjustments to look good.
|
||||
|
||||
#### Changing seek bar progress colors
|
||||
<!-- TODO: Is there a better way to do this? (The actual thing, not the tutorial) -->
|
||||
The seek bar consist of three segments: past (already played part of the presentation),
|
||||
future-buffered and future-unbuffered. The segments colors are set when the seek bar is created.
|
||||
To customize the colors, change the values of `shaka.ui.Controls.SEEK_BAR_BASE_COLOR_` ,
|
||||
`shaka.ui.Controls.SEEK_BAR_PLAYED_COLOR_`, and `shaka.ui.Controls.SEEK_BAR_BUFFERED_COLOR_` in ui/controls.js
|
||||
|
||||
<!-- TODO: If we add more buttons that can be put in the order this way, list them here. -->
|
||||
|
||||
<!-- TODO: Add a custom button tutorial. -->
|
||||
|
||||
#### Continue the Tutorials
|
||||
|
||||
Next, check out {@tutorial config}.
|
||||
|
||||
[UIConfiguration]: https://shaka-player-demo.appspot.com/docs/api/shaka.extern.html#.UIConfiguration
|
||||
@@ -0,0 +1,37 @@
|
||||
/**
|
||||
* @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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Externs for mux.js library.
|
||||
* @externs
|
||||
*/
|
||||
|
||||
|
||||
/** @const */
|
||||
var less = {};
|
||||
|
||||
|
||||
less.registerStylesheetsImmediately = function() {};
|
||||
|
||||
/**
|
||||
* @param {boolean} reload
|
||||
* @param {boolean} modifyVars
|
||||
* @param {boolean} clearFileCache
|
||||
* @return {!Promise}
|
||||
*/
|
||||
less.refresh = function(reload, modifyVars, clearFileCache) {};
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
/**
|
||||
* @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.
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* @externs
|
||||
* @suppress {duplicate} To prevent compiler errors with the namespace
|
||||
* being declared both here and by goog.provide in the library.
|
||||
*/
|
||||
|
||||
/** @namespace */
|
||||
var shaka = {};
|
||||
|
||||
/** @namespace */
|
||||
shaka.extern = {};
|
||||
|
||||
|
||||
/**
|
||||
* @typedef {{
|
||||
* controlPanelElements: !Array.<string>,
|
||||
* overflowMenuButtons: !Array.<string>,
|
||||
* addSeekBar: boolean,
|
||||
* adaptPlayButtonSize: boolean,
|
||||
* castReceiverAppId: string
|
||||
* }}
|
||||
*
|
||||
* @property {!Array.<string>} controlPanelElements
|
||||
* The ordered list of control panel elements of the UI.
|
||||
* @property {!Array.<string>} overflowMenuButtons
|
||||
* The ordered list of the overflow menu buttons.
|
||||
* @property {boolean} addSeekBar
|
||||
* Whether or not a seek bar should be part of the UI.
|
||||
* @property {boolean} adaptPlayButtonSize
|
||||
* Whether or not play button size should be dynamically adjusted to
|
||||
* the size of the video element.
|
||||
* @property {string} castReceiverAppId
|
||||
* Receiver app id to use for the Chromecast support.
|
||||
* @exportDoc
|
||||
*/
|
||||
shaka.extern.UIConfiguration;
|
||||
+7
-1
@@ -107,9 +107,12 @@ module.exports = function(config) {
|
||||
|
||||
// source files - these are only watched and served
|
||||
{pattern: 'lib/**/*.js', included: false},
|
||||
{pattern: 'ui/**/*.js', included: false},
|
||||
{pattern: 'ui/**/*.css', included: false},
|
||||
{pattern: 'ui/**/*.less', included: false},
|
||||
{pattern: 'third_party/closure/goog/**/*.js', included: false},
|
||||
{pattern: 'test/test/assets/*', included: false},
|
||||
{pattern: 'dist/shaka-player.compiled.js', included: false},
|
||||
{pattern: 'dist/shaka-player.ui.js', included: false},
|
||||
{pattern: 'node_modules/**/*.js', included: false},
|
||||
],
|
||||
|
||||
@@ -122,9 +125,12 @@ module.exports = function(config) {
|
||||
'lib/!(debug|polyfill)/*.js': ['coverage'],
|
||||
// Player is not matched by the above, so add it explicitly
|
||||
'lib/player.js': ['coverage'],
|
||||
// Compute coverage over UI, too
|
||||
'ui/*.js': ['coverage'],
|
||||
|
||||
// Convert ES6 to ES5 so we can still run tests on IE11.
|
||||
'lib/**/*.js': ['babel'],
|
||||
'ui/**/*.js': ['babel'],
|
||||
'test/**/*.js': ['babel'],
|
||||
},
|
||||
|
||||
|
||||
@@ -127,11 +127,11 @@ shaka.cast.CastProxy.prototype.destroy = function(forceDisconnect) {
|
||||
* elements as appropriate.
|
||||
*
|
||||
* @suppress {invalidCasts} to cast proxy Objects to unrelated types
|
||||
* @return {HTMLMediaElement}
|
||||
* @return {!HTMLMediaElement}
|
||||
* @export
|
||||
*/
|
||||
shaka.cast.CastProxy.prototype.getVideo = function() {
|
||||
return /** @type {HTMLMediaElement} */(this.videoProxy_);
|
||||
return /** @type {!HTMLMediaElement} */(this.videoProxy_);
|
||||
};
|
||||
|
||||
|
||||
@@ -140,11 +140,11 @@ shaka.cast.CastProxy.prototype.getVideo = function() {
|
||||
* as appropriate.
|
||||
*
|
||||
* @suppress {invalidCasts} to cast proxy Objects to unrelated types
|
||||
* @return {shaka.Player}
|
||||
* @return {!shaka.Player}
|
||||
* @export
|
||||
*/
|
||||
shaka.cast.CastProxy.prototype.getPlayer = function() {
|
||||
return /** @type {shaka.Player} */(this.playerProxy_);
|
||||
return /** @type {!shaka.Player} */(this.playerProxy_);
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -42,6 +42,7 @@ shaka.cast.CastUtils.VideoEvents = [
|
||||
'seeking',
|
||||
'timeupdate',
|
||||
'volumechange',
|
||||
'resize',
|
||||
];
|
||||
|
||||
|
||||
@@ -102,6 +103,8 @@ shaka.cast.CastUtils.PlayerEvents = [
|
||||
'timelineregionexit',
|
||||
'trackschanged',
|
||||
'unloading',
|
||||
'variantchanged',
|
||||
'textchanged',
|
||||
];
|
||||
|
||||
|
||||
|
||||
+81
-6
@@ -405,6 +405,28 @@ shaka.Player.version = 'v2.5.0-beta2-master-uncompiled';
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* @event shaka.Player.VariantChangedEvent
|
||||
* @description Fired when a call from the application caused a variant change.
|
||||
* Can be triggered by calls to selectVariantTrack() or selectAudioLanguage().
|
||||
* Does not fire when an automatic adaptation causes a variant change.
|
||||
* @property {string} type
|
||||
* 'variantchanged'
|
||||
* @exportDoc
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* @event shaka.Player.TextChangedEvent
|
||||
* @description Fired when a call from the application caused a text stream
|
||||
* change. Can be triggered by calls to selectTextTrack() or
|
||||
* selectTextLanguage().
|
||||
* @property {string} type
|
||||
* 'textchanged'
|
||||
* @exportDoc
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* @event shaka.Player.ExpirationUpdatedEvent
|
||||
* @description Fired when there is a change in the expiration times of an
|
||||
@@ -1882,9 +1904,14 @@ shaka.Player.prototype.isTextTrackVisible = function() {
|
||||
* Set the visibility of the current text track, if any.
|
||||
*
|
||||
* @param {boolean} on
|
||||
* @return {!Promise}
|
||||
* @export
|
||||
*/
|
||||
shaka.Player.prototype.setTextTrackVisibility = function(on) {
|
||||
shaka.Player.prototype.setTextTrackVisibility = async function(on) {
|
||||
if (on == this.textVisibility_) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.textDisplayer_) {
|
||||
this.textDisplayer_.setTextVisibility(on);
|
||||
}
|
||||
@@ -1892,11 +1919,16 @@ shaka.Player.prototype.setTextTrackVisibility = function(on) {
|
||||
this.onTextTrackVisibility_();
|
||||
|
||||
// If we always stream text, don't do anything special to StreamingEngine.
|
||||
if (this.config_.streaming.alwaysStreamText) return;
|
||||
if (this.config_.streaming.alwaysStreamText) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Load text stream when the user chooses to show the caption, and pause
|
||||
// loading text stream when the user chooses to hide the caption.
|
||||
if (!this.streamingEngine_) return;
|
||||
if (!this.streamingEngine_) {
|
||||
return;
|
||||
}
|
||||
|
||||
const StreamUtils = shaka.util.StreamUtils;
|
||||
|
||||
if (on) {
|
||||
@@ -1907,7 +1939,7 @@ shaka.Player.prototype.setTextTrackVisibility = function(on) {
|
||||
this.currentTextRole_);
|
||||
let stream = textStreams[0];
|
||||
if (stream) {
|
||||
this.streamingEngine_.loadNewTextStream(stream);
|
||||
await this.streamingEngine_.loadNewTextStream(stream);
|
||||
}
|
||||
} else {
|
||||
this.streamingEngine_.unloadTextStream();
|
||||
@@ -2554,6 +2586,8 @@ shaka.Player.prototype.switchVariant_ =
|
||||
} else {
|
||||
// Act now.
|
||||
this.streamingEngine_.switchVariant(variant, clearBuffer, safeMargin);
|
||||
// Dispatch a 'variantchanged' event
|
||||
this.onVariantChanged_();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -2570,6 +2604,7 @@ shaka.Player.prototype.switchTextStream_ = function(textStream) {
|
||||
} else {
|
||||
// Act now.
|
||||
this.streamingEngine_.switchTextStream(textStream);
|
||||
this.onTextChanged_();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -2915,7 +2950,7 @@ shaka.Player.prototype.chooseStreams_ = function(manifest, period) {
|
||||
const StreamUtils = shaka.util.StreamUtils;
|
||||
|
||||
goog.asserts.assert(
|
||||
manifest.periods.indexOf(period) >= 0,
|
||||
manifest.periods.includes(period),
|
||||
'The period should be part of the manifest.');
|
||||
|
||||
// We are switching Periods, so the AbrManager will be disabled. But if we
|
||||
@@ -2969,7 +3004,7 @@ shaka.Player.prototype.chooseStreams_ = function(manifest, period) {
|
||||
const chosenAudio = chosenVariant ? chosenVariant.audio : null;
|
||||
if (startingUp && chosenAudio && chosenText) {
|
||||
if (this.shouldShowText_(chosenAudio, chosenText)) {
|
||||
this.textDisplayer_.setTextVisibility(true);
|
||||
this.setTextTrackVisibility(true);
|
||||
this.onTextTrackVisibility_();
|
||||
}
|
||||
}
|
||||
@@ -3135,6 +3170,46 @@ shaka.Player.prototype.onTracksChanged_ = async function() {
|
||||
// them.
|
||||
await this.waitNextTick_();
|
||||
|
||||
if (this.destroyer_.destroyed()) {
|
||||
return;
|
||||
}
|
||||
|
||||
let event = new shaka.util.FakeEvent('trackschanged');
|
||||
this.dispatchEvent(event);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Dispatches a 'variantchanged' event.
|
||||
* @return {!Promise}
|
||||
* @private
|
||||
*/
|
||||
shaka.Player.prototype.onVariantChanged_ = async function() {
|
||||
// Dispatch a 'trackschanged' event next interpreter cycle. This gives
|
||||
// StreamingEngine time to absorb the changes before the user tries to query
|
||||
// them.
|
||||
await this.waitNextTick_();
|
||||
|
||||
if (this.destroyer_.destroyed()) {
|
||||
return;
|
||||
}
|
||||
|
||||
let event = new shaka.util.FakeEvent('variantchanged');
|
||||
this.dispatchEvent(event);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Dispatches a 'textchanged' event.
|
||||
* @return {!Promise}
|
||||
* @private
|
||||
*/
|
||||
shaka.Player.prototype.onTextChanged_ = async function() {
|
||||
// Dispatch a 'textchanged' event next interpreter cycle. This gives
|
||||
// StreamingEngine time to absorb the changes before the user tries to query
|
||||
// them.
|
||||
await this.waitNextTick_();
|
||||
|
||||
if (this.destroyer_.destroyed()) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ goog.require('shaka.log');
|
||||
* contains an Object, it is used as the template.
|
||||
* @param {string} path to this part of the config
|
||||
* @return {boolean}
|
||||
* @export
|
||||
*/
|
||||
shaka.util.ConfigUtils.mergeConfigObjects =
|
||||
function(destination, source, template, overrides, path) {
|
||||
|
||||
@@ -216,6 +216,48 @@ shaka.util.LanguageUtils = class {
|
||||
language;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if two language codes are siblings. Language codes are siblings if
|
||||
* they share the same base language while neither one is the base language.
|
||||
*
|
||||
* For example, "en-US" and "en-CA" are siblings but "en-US" and "en" are not
|
||||
* siblings.
|
||||
*
|
||||
* @param {string} a
|
||||
* @param {string} b
|
||||
* @return {boolean}
|
||||
*/
|
||||
static areSiblings(a, b) {
|
||||
const baseA = shaka.util.LanguageUtils.getBase(a);
|
||||
const baseB = shaka.util.LanguageUtils.getBase(b);
|
||||
|
||||
return a != baseA && b != baseB && baseA == baseB;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the normalized base language for a language code.
|
||||
*
|
||||
* @param {string} lang
|
||||
* @return {string}
|
||||
*/
|
||||
static getBase(lang) {
|
||||
const splitAt = lang.indexOf('-');
|
||||
let major;
|
||||
|
||||
if (splitAt >= 0) {
|
||||
major = lang.substring(0, splitAt);
|
||||
} else {
|
||||
major = lang;
|
||||
}
|
||||
|
||||
// Convert the major code to lower case. It is standard for the major code
|
||||
// to be in lower case, but it will also make the map look-up easier.
|
||||
major = major.toLowerCase();
|
||||
major = shaka.util.LanguageUtils.isoMap_.get(major) || major;
|
||||
|
||||
return major;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the normalized language of the given text stream. Will return 'und' if
|
||||
* a language is not found on the text stream.
|
||||
@@ -409,4 +451,3 @@ shaka.util.LanguageUtils.isoMap_ = new Map([
|
||||
['wel', 'cy'], ['wln', 'wa'], ['wol', 'wo'], ['xho', 'xh'], ['yid', 'yi'],
|
||||
['yor', 'yo'], ['zha', 'za'], ['zho', 'zh'], ['zul', 'zu'],
|
||||
]);
|
||||
|
||||
|
||||
@@ -64,3 +64,35 @@ shaka.util.MapUtils.asObject = function(map) {
|
||||
|
||||
return obj;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {!Object.<KEY, VALUE>} object
|
||||
* @return {!Map.<KEY, VALUE>}
|
||||
* @template KEY,VALUE
|
||||
* @private
|
||||
*/
|
||||
shaka.util.MapUtils.asMap_ = function(object) {
|
||||
const map = new Map();
|
||||
Object.keys(object).forEach((key) => {
|
||||
map.set(key, object[key]);
|
||||
});
|
||||
|
||||
return map;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {!Map.<KEY, VALUE>} map
|
||||
* @return {!Object.<KEY, VALUE>}
|
||||
* @template KEY,VALUE
|
||||
* @private
|
||||
*/
|
||||
shaka.util.MapUtils.asObject_ = function(map) {
|
||||
const obj = {};
|
||||
map.forEach((value, key) => {
|
||||
obj[key] = value;
|
||||
});
|
||||
|
||||
return obj;
|
||||
};
|
||||
|
||||
@@ -23,6 +23,7 @@ goog.provide('shaka.util.Timer');
|
||||
* @param {Function} callback
|
||||
* @constructor
|
||||
* @struct
|
||||
* @export
|
||||
*/
|
||||
shaka.util.Timer = function(callback) {
|
||||
/** @private {?number} */
|
||||
@@ -38,6 +39,7 @@ shaka.util.Timer = function(callback) {
|
||||
|
||||
/**
|
||||
* Cancel the timer, if it's running.
|
||||
* @export
|
||||
*/
|
||||
shaka.util.Timer.prototype.cancel = function() {
|
||||
if (this.id_ != null) {
|
||||
@@ -50,6 +52,7 @@ shaka.util.Timer.prototype.cancel = function() {
|
||||
/**
|
||||
* Schedule the timer, canceling any previous scheduling.
|
||||
* @param {number} seconds
|
||||
* @export
|
||||
*/
|
||||
shaka.util.Timer.prototype.schedule = function(seconds) {
|
||||
this.cancel();
|
||||
@@ -61,6 +64,7 @@ shaka.util.Timer.prototype.schedule = function(seconds) {
|
||||
* Schedule the timer, canceling any previous scheduling. The timer will
|
||||
* automatically reschedule after the callback fires.
|
||||
* @param {number} seconds
|
||||
* @export
|
||||
*/
|
||||
shaka.util.Timer.prototype.scheduleRepeated = function(seconds) {
|
||||
this.cancel();
|
||||
|
||||
@@ -40,6 +40,7 @@
|
||||
"karma-source-map-support": "^1.2.0",
|
||||
"karma-spec-reporter": "~0.0.31",
|
||||
"karma-webdriver-launcher": "~1.0.5",
|
||||
"less": "^3.8.1",
|
||||
"mux.js": "~4.5.1",
|
||||
"promise-mock": "^2.1.0",
|
||||
"rimraf": "~2.6.1",
|
||||
|
||||
@@ -60,4 +60,7 @@ goog.require('shaka.text.SimpleTextDisplayer');
|
||||
goog.require('shaka.text.TextEngine');
|
||||
goog.require('shaka.text.TtmlTextParser');
|
||||
goog.require('shaka.text.VttTextParser');
|
||||
goog.require('shaka.ui.Localization');
|
||||
goog.require('shaka.ui.Overlay');
|
||||
goog.require('shaka.util.Error');
|
||||
goog.require('shaka.util.Iterables');
|
||||
|
||||
@@ -49,7 +49,7 @@ describe('Player', function() {
|
||||
} else {
|
||||
// Load the compiled library as a module.
|
||||
// All tests in this suite will use the compiled library.
|
||||
require(['/base/dist/shaka-player.compiled.js'], (shakaModule) => {
|
||||
require(['/base/dist/shaka-player.ui.js'], (shakaModule) => {
|
||||
compiledShaka = shakaModule;
|
||||
loaded.resolve();
|
||||
});
|
||||
|
||||
@@ -46,7 +46,7 @@ describe('Player', function() {
|
||||
} else {
|
||||
// Load the compiled library as a module.
|
||||
// All tests in this suite will use the compiled library.
|
||||
require(['/base/dist/shaka-player.compiled.js'], (shakaModule) => {
|
||||
require(['/base/dist/shaka-player.ui.js'], (shakaModule) => {
|
||||
compiledShaka = shakaModule;
|
||||
compiledShaka.net.NetworkingEngine.registerScheme(
|
||||
'test', shaka.test.TestScheme);
|
||||
|
||||
@@ -519,7 +519,6 @@ describe('Player', function() {
|
||||
await player.load(fakeManifestUri, 0, returnManifest(manifest));
|
||||
player.setTextTrackVisibility(false);
|
||||
expect(streamingEngine.loadNewTextStream).not.toHaveBeenCalled();
|
||||
expect(streamingEngine.unloadTextStream).toHaveBeenCalled();
|
||||
expect(streamingEngine.getActiveText()).toBe(null);
|
||||
});
|
||||
|
||||
|
||||
+7
-1
@@ -189,13 +189,19 @@ function getClientArg(name) {
|
||||
name: 'sprintf-js',
|
||||
main: 'src/sprintf',
|
||||
},
|
||||
{
|
||||
name: 'less',
|
||||
main: 'dist/less',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
// Load required AMD modules, then proceed with tests.
|
||||
require(['promise-mock', 'sprintf-js'], (PromiseMock, sprintfJs) => {
|
||||
require(['promise-mock', 'sprintf-js', 'less'],
|
||||
(PromiseMock, sprintfJs, less) => {
|
||||
window.PromiseMock = PromiseMock;
|
||||
window.sprintf = sprintfJs.sprintf;
|
||||
window.less = less;
|
||||
|
||||
// Patch a new convenience method into PromiseMock.
|
||||
// See https://github.com/taylorhakes/promise-mock/issues/7
|
||||
|
||||
@@ -22,8 +22,8 @@
|
||||
|
||||
|
||||
/**
|
||||
* @param {!Array.<string>} moduleNames
|
||||
* @param {Function} callback
|
||||
* @param {!Array.<string>|string} moduleNames
|
||||
* @param {Function=} callback
|
||||
*/
|
||||
var require = function(moduleNames, callback) {};
|
||||
|
||||
|
||||
@@ -0,0 +1,518 @@
|
||||
/**
|
||||
* @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.
|
||||
*/
|
||||
|
||||
describe('Localization', function() {
|
||||
const Localization = shaka.ui.Localization;
|
||||
|
||||
// https://bit.ly/2PbzxlD
|
||||
const DWARFISH = 'dwarfish';
|
||||
const DWARFISH_NORTH = 'dwarfish-NORTH';
|
||||
const DWARFISH_SOUTH = 'dwarfish-SOUTH';
|
||||
|
||||
const ELVISH = 'elvish';
|
||||
const ELVISH_WOODLAND = 'elvish-WOODLAND';
|
||||
|
||||
const HALFLING = 'halfling';
|
||||
const HALFLING_COMMON = 'halfling-COMMON';
|
||||
const HALFLING_RIVER = 'halfling-RIVER';
|
||||
const HALFLING_SHIRE = 'halfling-SHIRE';
|
||||
|
||||
describe('insert', function() {
|
||||
const EN_US = 'en-US';
|
||||
|
||||
it('can add new data', function() {
|
||||
/** @type {!shaka.ui.Localization} */
|
||||
const localization = new Localization(EN_US);
|
||||
localization.insert(EN_US, new Map([
|
||||
['hello', 'howdy'],
|
||||
['good-bye', 'cheerio'],
|
||||
]));
|
||||
|
||||
expect(localization.resolve('hello')).toBe('howdy');
|
||||
expect(localization.resolve('good-bye')).toBe('cheerio');
|
||||
});
|
||||
|
||||
it('can replace old data when adding new data', function() {
|
||||
/** @type {!shaka.ui.Localization} */
|
||||
const localization = new Localization(EN_US);
|
||||
localization.insert(EN_US, new Map([
|
||||
['hello', 'howdy'],
|
||||
['good-bye', 'cheerio'],
|
||||
]));
|
||||
|
||||
expect(localization.resolve('hello')).toBe('howdy');
|
||||
expect(localization.resolve('good-bye')).toBe('cheerio');
|
||||
|
||||
localization.insert(EN_US, new Map([
|
||||
['good-bye', 'farewell'],
|
||||
['thank-you', 'thank-you'],
|
||||
]));
|
||||
|
||||
expect(localization.resolve('hello')).toBe('howdy');
|
||||
expect(localization.resolve('good-bye')).toBe('farewell');
|
||||
expect(localization.resolve('thank-you')).toBe('thank-you');
|
||||
});
|
||||
|
||||
it('can keep old data when adding new', function() {
|
||||
const USE_OLD = Localization.ConflictResolution.USE_OLD;
|
||||
|
||||
/** @type {!shaka.ui.Localization} */
|
||||
const localization = new Localization(EN_US);
|
||||
localization.insert(EN_US, new Map([
|
||||
['hello', 'howdy'],
|
||||
['good-bye', 'cheerio'],
|
||||
]));
|
||||
|
||||
expect(localization.resolve('hello')).toBe('howdy');
|
||||
expect(localization.resolve('good-bye')).toBe('cheerio');
|
||||
|
||||
localization.insert(EN_US, new Map([
|
||||
['hello', 'greetings'],
|
||||
['good-bye', 'farewell'],
|
||||
['thank-you', 'thank-you'],
|
||||
]), USE_OLD);
|
||||
|
||||
// Nothing should have changed.
|
||||
expect(localization.resolve('hello')).toBe('howdy');
|
||||
expect(localization.resolve('good-bye')).toBe('cheerio');
|
||||
expect(localization.resolve('thank-you')).toBe('thank-you');
|
||||
});
|
||||
});
|
||||
|
||||
describe('locale fallback', () => {
|
||||
const KEY = 'KEY';
|
||||
|
||||
const FALLBACK = 'FALLBACK';
|
||||
const FALLBACK_VALUE = 'FALLBACK VALUE';
|
||||
|
||||
const ELVISH_VALUE = 'ELVISH VALUE';
|
||||
|
||||
const HALFLING_COMMON_VALUE = 'HALFLING-COMMON VALUE';
|
||||
const HALFLING_SHIRE_VALUE = 'HALFLING-SHIRE VALUE';
|
||||
|
||||
/** @type {!shaka.ui.Localization} */
|
||||
let localization;
|
||||
|
||||
const insert = (locale, value) => {
|
||||
localization.insert(locale, new Map([[KEY, value]]));
|
||||
};
|
||||
|
||||
const testLocales = (locales, expectedValue) => {
|
||||
localization.changeLocale(locales);
|
||||
const value = localization.resolve(KEY);
|
||||
expect(value).toEqual(expectedValue);
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
localization = new Localization(FALLBACK);
|
||||
|
||||
// Insert translations to test various scenarios below:
|
||||
insert(FALLBACK, FALLBACK_VALUE);
|
||||
insert(ELVISH, ELVISH_VALUE);
|
||||
insert(HALFLING_COMMON, HALFLING_COMMON_VALUE);
|
||||
insert(HALFLING_SHIRE, HALFLING_SHIRE_VALUE);
|
||||
});
|
||||
|
||||
it('resorts to final fallback for one unknown locale', () => {
|
||||
// For one locale we don't have translations for, go to the fallback.
|
||||
testLocales([DWARFISH_NORTH], FALLBACK_VALUE);
|
||||
});
|
||||
|
||||
it('resorts to final fallback for multiple unknown locales', () => {
|
||||
// For multiple locales in which we don't have translations, go to the
|
||||
// fallback.
|
||||
testLocales([
|
||||
DWARFISH_NORTH,
|
||||
DWARFISH_SOUTH,
|
||||
], FALLBACK_VALUE);
|
||||
});
|
||||
|
||||
it('prefers a known locale from the list instead of final fallback', () => {
|
||||
// If we have a locale in the list for which we _do_ have translations,
|
||||
// expect to see the translated value.
|
||||
testLocales([
|
||||
DWARFISH_NORTH,
|
||||
DWARFISH_SOUTH,
|
||||
ELVISH,
|
||||
], ELVISH_VALUE);
|
||||
});
|
||||
|
||||
it('prefers the first known locale from the list', () => {
|
||||
// If we have multiple locales in the list for which we _do_ have
|
||||
// translations, expect to see the translated value from the first one.
|
||||
testLocales([
|
||||
DWARFISH_NORTH,
|
||||
DWARFISH_SOUTH,
|
||||
HALFLING_COMMON,
|
||||
ELVISH,
|
||||
], HALFLING_COMMON_VALUE);
|
||||
});
|
||||
|
||||
it('finds translations from the parent locale', () => {
|
||||
// If we have a locale in the list for which we have a region-less
|
||||
// translation, expect to see the translated value.
|
||||
testLocales([ELVISH_WOODLAND], ELVISH_VALUE);
|
||||
});
|
||||
|
||||
it('finds translations from a locale with a different region', () => {
|
||||
// If there's a locale in the list for which we have translations in a
|
||||
// sibling locale (same language, different region), expect to see that
|
||||
// value. Common is chosen over shire because equivalent-distance locales
|
||||
// are alphabetically sorted to break ties.
|
||||
testLocales([HALFLING_RIVER], HALFLING_COMMON_VALUE);
|
||||
});
|
||||
|
||||
it('finds translations from a child locale', () => {
|
||||
// If there's a locale in the list for which we have translations in a
|
||||
// child locale (same language, but for a specific region), expect to see
|
||||
// that value. Common is chosen over shire because equivalent-distance
|
||||
// locales are alphabetically sorted to break ties.
|
||||
testLocales([HALFLING], HALFLING_COMMON_VALUE);
|
||||
});
|
||||
|
||||
it('prefers a relative of an earlier locale to an exact later one', () => {
|
||||
// If we have multiple locales in the list, and an earlier one has
|
||||
// relatives with translations, expect to see that chosen over an exact
|
||||
// match later in the list.
|
||||
testLocales([
|
||||
HALFLING,
|
||||
ELVISH,
|
||||
], HALFLING_COMMON_VALUE);
|
||||
});
|
||||
});
|
||||
|
||||
describe('unknown locales event', function() {
|
||||
it('fires when we change to a locale we have not loaded', function() {
|
||||
const events = [];
|
||||
|
||||
const localization = new Localization(/* fallback */ HALFLING_COMMON);
|
||||
collectEvents(localization, Localization.UNKNOWN_LOCALES, events);
|
||||
|
||||
localization.insert(HALFLING_COMMON, new Map());
|
||||
localization.changeLocale([ELVISH_WOODLAND]);
|
||||
|
||||
expect(events.length).toBe(1);
|
||||
expect(events[0].locales).toEqual([ELVISH_WOODLAND]);
|
||||
});
|
||||
|
||||
|
||||
it('will not fire after we add the locale', function() {
|
||||
const events = [];
|
||||
|
||||
const localization = new Localization(/* fallback */ HALFLING_COMMON);
|
||||
collectEvents(localization, Localization.UNKNOWN_LOCALES, events);
|
||||
|
||||
// We should see an event telling us that both elvish and dwarfish are
|
||||
// missing.
|
||||
localization.insert(HALFLING_COMMON, new Map());
|
||||
localization.changeLocale([ELVISH_WOODLAND, DWARFISH_NORTH]);
|
||||
|
||||
expect(events.length).toBe(1);
|
||||
expect(events[0].locales).toEqual([ELVISH_WOODLAND, DWARFISH_NORTH]);
|
||||
|
||||
// We should see an event telling us that dwarfish is still missing.
|
||||
localization.insert(ELVISH_WOODLAND, new Map());
|
||||
localization.changeLocale([ELVISH_WOODLAND, DWARFISH_NORTH]);
|
||||
|
||||
expect(events.length).toBe(2);
|
||||
expect(events[0].locales).toEqual([ELVISH_WOODLAND, DWARFISH_NORTH]);
|
||||
expect(events[1].locales).toEqual([DWARFISH_NORTH]);
|
||||
|
||||
// There should be no more events now that we added the last language.
|
||||
localization.insert(DWARFISH_NORTH, new Map());
|
||||
localization.changeLocale([ELVISH_WOODLAND, DWARFISH_NORTH]);
|
||||
|
||||
expect(events.length).toBe(2);
|
||||
expect(events[0].locales).toEqual([ELVISH_WOODLAND, DWARFISH_NORTH]);
|
||||
expect(events[1].locales).toEqual([DWARFISH_NORTH]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('unknown localization event', function() {
|
||||
const HOW_ARE_YOU = 'how are you';
|
||||
|
||||
const HALFLING_COMMON_MAP = new Map().set(
|
||||
HOW_ARE_YOU, 'How ye this topa the mornin?');
|
||||
const HALFLING_SHIRE_MAP = new Map().set(
|
||||
HOW_ARE_YOU, 'How be the day treating ya?');
|
||||
const DWARFISH_MAP = new Map().set(
|
||||
HOW_ARE_YOU, 'You alive?');
|
||||
const DWARFISH_NORTH_MAP = new Map().set(
|
||||
HOW_ARE_YOU, 'Is the fire alive in ye soul?');
|
||||
const DWARFISH_SOUTH_MAP = new Map().set(
|
||||
HOW_ARE_YOU, 'You dead yet?');
|
||||
|
||||
/** @type {!shaka.ui.Localization} */
|
||||
let localization;
|
||||
|
||||
beforeEach(function() {
|
||||
localization = new shaka.ui.Localization(HALFLING_COMMON);
|
||||
|
||||
// Insert every locale into the system but leave them empty. Each test
|
||||
// will add entries as they need them.
|
||||
const emptyMap = new Map();
|
||||
localization.insert(DWARFISH, emptyMap);
|
||||
localization.insert(DWARFISH_NORTH, emptyMap);
|
||||
localization.insert(DWARFISH_SOUTH, emptyMap);
|
||||
localization.insert(HALFLING_COMMON, emptyMap);
|
||||
localization.insert(HALFLING_SHIRE, emptyMap);
|
||||
});
|
||||
|
||||
it('will not fire when key is found in preferred', function() {
|
||||
localization.insert(DWARFISH_NORTH, DWARFISH_NORTH_MAP);
|
||||
localization.changeLocale([DWARFISH_NORTH]);
|
||||
|
||||
const events = [];
|
||||
collectEvents(localization, Localization.UNKNOWN_LOCALIZATION, events);
|
||||
|
||||
// We expect to see a non-empty string returned and to see NO "missing
|
||||
// localization" events.
|
||||
expect(localization.resolve(HOW_ARE_YOU)).toBeTruthy();
|
||||
expect(events.length).toBe(0);
|
||||
});
|
||||
|
||||
it('will not fire when key is found in base', function() {
|
||||
localization.insert(DWARFISH, DWARFISH_MAP);
|
||||
localization.changeLocale([DWARFISH_NORTH]);
|
||||
|
||||
const events = [];
|
||||
collectEvents(localization, Localization.UNKNOWN_LOCALIZATION, events);
|
||||
|
||||
// We expect to see a non-empty string returned and to see NO "missing
|
||||
// localization" events.
|
||||
expect(localization.resolve(HOW_ARE_YOU)).toBeTruthy();
|
||||
expect(events.length).toBe(0);
|
||||
});
|
||||
|
||||
it('will not fire when key is found in sibling', function() {
|
||||
localization.insert(DWARFISH_SOUTH, DWARFISH_SOUTH_MAP);
|
||||
localization.changeLocale([DWARFISH_NORTH]);
|
||||
|
||||
const events = [];
|
||||
collectEvents(localization, Localization.UNKNOWN_LOCALIZATION, events);
|
||||
|
||||
// We expect to see a non-empty string returned and to see NO "missing
|
||||
// localization" events.
|
||||
expect(localization.resolve(HOW_ARE_YOU)).toBeTruthy();
|
||||
expect(events.length).toBe(0);
|
||||
});
|
||||
|
||||
it('will not fire when key is found in fallback', function() {
|
||||
localization.insert(HALFLING_COMMON, HALFLING_COMMON_MAP);
|
||||
localization.changeLocale([DWARFISH_NORTH]);
|
||||
|
||||
const events = [];
|
||||
collectEvents(localization, Localization.UNKNOWN_LOCALIZATION, events);
|
||||
|
||||
// We expect to see a non-empty string returned and to see NO "missing
|
||||
// localization" events.
|
||||
expect(localization.resolve(HOW_ARE_YOU)).toBeTruthy();
|
||||
expect(events.length).toBe(0);
|
||||
});
|
||||
|
||||
it('fires when key is not found', function() {
|
||||
localization.changeLocale([DWARFISH_NORTH]);
|
||||
|
||||
const events = [];
|
||||
collectEvents(localization, Localization.UNKNOWN_LOCALIZATION, events);
|
||||
|
||||
// When nothing is found an empty string should be returned and we should
|
||||
// see a "missing localization" event.
|
||||
expect(localization.resolve(HOW_ARE_YOU)).toBe('');
|
||||
expect(events.length).toBe(1);
|
||||
});
|
||||
|
||||
// This test is similar to "missing from everything", but the difference
|
||||
// here is that the entry can be found in a locale that is not part of
|
||||
// search.
|
||||
it('fires when key is not found in fallback, siblings, base, or preferred',
|
||||
function() {
|
||||
localization.insert(HALFLING_SHIRE, HALFLING_SHIRE_MAP);
|
||||
localization.changeLocale([DWARFISH_NORTH]);
|
||||
|
||||
const events = [];
|
||||
collectEvents(
|
||||
localization, Localization.UNKNOWN_LOCALIZATION, events);
|
||||
|
||||
// When nothing is found an empty string should be returned and we
|
||||
// should see a "missing localization" event.
|
||||
expect(localization.resolve(HOW_ARE_YOU)).toBe('');
|
||||
expect(events.length).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
// The "missing localizations event" is fired when all preferred locales do
|
||||
// not have the requested localization but a related locale does have the
|
||||
// requested localization
|
||||
describe('missing localizations event', function() {
|
||||
it('fires when key/value is missing from preferred but found in fallback',
|
||||
function() {
|
||||
// Initialize the localization system so that we have an entry for
|
||||
// "hello" in our fallback, but not in our preferred locale.
|
||||
const localization = new Localization(HALFLING_COMMON);
|
||||
localization.insert(HALFLING_COMMON, new Map([
|
||||
['hello', 'Hallo! Hallo! And who may you be?'],
|
||||
]));
|
||||
localization.insert(ELVISH_WOODLAND, new Map());
|
||||
|
||||
const events = [];
|
||||
collectEvents(
|
||||
localization, Localization.MISSING_LOCALIZATIONS, events);
|
||||
|
||||
// Change locales should cause the missing localizations event to fire
|
||||
// and report that we are missing an entry for "hello".
|
||||
localization.changeLocale([ELVISH_WOODLAND]);
|
||||
|
||||
// We should be told that we are missing an entry for "hello" because
|
||||
// it was in the fallback (HALFLING) but not our preferred language
|
||||
// (EVLISH).
|
||||
expect(events.length).toBe(1);
|
||||
expect(events[0].locales).toEqual([ELVISH_WOODLAND]);
|
||||
expect(events[0].missing).toEqual(['hello']);
|
||||
});
|
||||
|
||||
it('fires when key/value is missing from preferred but found in base',
|
||||
function() {
|
||||
// Initialize the localization system so that we have an entry for
|
||||
// "Best Wishes" in our base language, but not in our preferred
|
||||
// locale.
|
||||
const localization = new Localization(HALFLING_COMMON);
|
||||
localization.insert(ELVISH_WOODLAND, new Map());
|
||||
localization.insert(ELVISH, new Map([
|
||||
['Best Wishes', 'Merin sa haryalye alasse'],
|
||||
]));
|
||||
|
||||
const events = [];
|
||||
collectEvents(
|
||||
localization, Localization.MISSING_LOCALIZATIONS, events);
|
||||
|
||||
// Change locales should cause the missing localizations event to fire
|
||||
// and report that we are missing an entry for "Best Wishes".
|
||||
localization.changeLocale([ELVISH_WOODLAND]);
|
||||
|
||||
// We should be told that we are missing an entry for "Best Wishes"
|
||||
// because it was in the base (ELVISH) but not our preferred language
|
||||
// (EVLISH_WOODLAND).
|
||||
expect(events.length).toBe(1);
|
||||
expect(events[0].locales).toEqual([ELVISH_WOODLAND]);
|
||||
expect(events[0].missing).toEqual(['Best Wishes']);
|
||||
});
|
||||
|
||||
it('fires when key/value is missing from preferred but found in sibling',
|
||||
function() {
|
||||
// Initialize the localization system so that we have an entry for
|
||||
// "Do you understand" in a language that shares a base language with
|
||||
// our preferred locale, but not in our preferred locale.
|
||||
const localization = new Localization(ELVISH_WOODLAND);
|
||||
localization.insert(HALFLING_COMMON, new Map());
|
||||
localization.insert(HALFLING_SHIRE, new Map([
|
||||
['Do you understand', 'If you take my meaning.'],
|
||||
]));
|
||||
|
||||
const events = [];
|
||||
collectEvents(
|
||||
localization, Localization.MISSING_LOCALIZATIONS, events);
|
||||
|
||||
// Change locales should cause the missing localizations event to fire
|
||||
// and report that we are missing an entry for "Best Wishes".
|
||||
localization.changeLocale([HALFLING_COMMON]);
|
||||
|
||||
// We should be told that we are missing an entry for "Do you
|
||||
// understand" because it was in a sibling locale (HALFLING_SHIRE)
|
||||
// but not our preferred language (HALFLING_COMMON).
|
||||
expect(events.length).toBe(1);
|
||||
expect(events[0].locales).toEqual([HALFLING_COMMON]);
|
||||
expect(events[0].missing).toEqual(['Do you understand']);
|
||||
});
|
||||
|
||||
it('fires when key/value is missing from some preferred langauges',
|
||||
function() {
|
||||
// Initialize the localization system so that we have an entry for
|
||||
// "may your forge burn bright" in our second preference but not our
|
||||
// first.
|
||||
const localization = new Localization(HALFLING_COMMON);
|
||||
localization.insert(HALFLING_COMMON, new Map());
|
||||
localization.insert(ELVISH_WOODLAND, new Map());
|
||||
localization.insert(DWARFISH_NORTH, new Map([
|
||||
['may your forge burn bright', 'tan menu selek lanun khun'],
|
||||
]));
|
||||
|
||||
const events = [];
|
||||
collectEvents(
|
||||
localization, Localization.MISSING_LOCALIZATIONS, events);
|
||||
|
||||
// Changing locales should not fire the missing localization event
|
||||
// because the localization for "may your forge burn bright" can
|
||||
// be found in our secondary preferred language.
|
||||
localization.changeLocale([ELVISH_WOODLAND, DWARFISH_NORTH]);
|
||||
|
||||
// There should have no missing localization events.
|
||||
expect(events.length).toBe(1);
|
||||
expect(events[0].locales).toEqual([ELVISH_WOODLAND, DWARFISH_NORTH]);
|
||||
expect(events[0].missing).toEqual(['may your forge burn bright']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('locale changed event', function() {
|
||||
it('fires when locale changes', function() {
|
||||
const localization = new Localization(HALFLING_COMMON);
|
||||
|
||||
const events = [];
|
||||
collectEvents(localization, Localization.LOCALE_CHANGED, events);
|
||||
|
||||
// We are going from nothing to something, we should see an event.
|
||||
localization.changeLocale([DWARFISH_SOUTH]);
|
||||
expect(events.length).toBe(1);
|
||||
|
||||
// We are changing from SOUTH to NORTH, we should see another event.
|
||||
localization.changeLocale([DWARFISH_NORTH]);
|
||||
expect(events.length).toBe(2);
|
||||
});
|
||||
|
||||
it('fires when changing to the same locale', function() {
|
||||
const localization = new Localization(HALFLING_COMMON);
|
||||
|
||||
const events = [];
|
||||
collectEvents(localization, Localization.LOCALE_CHANGED, events);
|
||||
|
||||
// We are going from nothing to something, we should see an event.
|
||||
localization.changeLocale([DWARFISH_SOUTH]);
|
||||
expect(events.length).toBe(1);
|
||||
|
||||
// We are going to ask to go our current locale, even through
|
||||
// conceptually this would be a no-op, the event should still fire.
|
||||
localization.changeLocale([DWARFISH_SOUTH]);
|
||||
expect(events.length).toBe(2);
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Listen to |localization| for specific events and save them in |out|.
|
||||
*
|
||||
* @param {!shaka.ui.Localization} localization
|
||||
* @param {string} eventName
|
||||
* @param {!Array.<!shaka.util.FakeEvent>} out
|
||||
*/
|
||||
function collectEvents(localization, eventName, out) {
|
||||
const onEvent = (event) => {
|
||||
const fakeEvent = /** @type {!shaka.util.FakeEvent} */ (event);
|
||||
out.push(fakeEvent);
|
||||
};
|
||||
|
||||
localization.addEventListener(eventName, onEvent);
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,647 @@
|
||||
/**
|
||||
* @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.
|
||||
*/
|
||||
|
||||
describe('UI', function() {
|
||||
/** @type {!shaka.Player} */
|
||||
let player;
|
||||
/** @type {!Element} */
|
||||
let cssLink;
|
||||
|
||||
beforeAll(async function() {
|
||||
// Add css file
|
||||
let head = document.head;
|
||||
cssLink = document.createElement('link');
|
||||
cssLink.type = 'text/css';
|
||||
cssLink.rel = 'stylesheet/less';
|
||||
cssLink.href ='/base/ui/controls.less';
|
||||
head.appendChild(cssLink);
|
||||
|
||||
// LESS script has been added at the beginning of the test pass
|
||||
// (in test/test/boot.js). This tells it that we've added a new
|
||||
// stylesheet, so LESS can process it.
|
||||
less.registerStylesheetsImmediately();
|
||||
await less.refresh(/* reload */ true,
|
||||
/* modifyVars*/ false, /* clearFileCache */ false);
|
||||
});
|
||||
|
||||
|
||||
afterAll(function() {
|
||||
document.head.removeChild(cssLink);
|
||||
});
|
||||
|
||||
describe('constructed through API', function() {
|
||||
/** @type {!HTMLElement} */
|
||||
let videoContainer;
|
||||
/** @type {!HTMLVideoElement} */
|
||||
let video;
|
||||
|
||||
beforeAll(function() {
|
||||
videoContainer =
|
||||
/** @type {!HTMLElement} */ (document.createElement('div'));
|
||||
document.body.appendChild(videoContainer);
|
||||
|
||||
video =
|
||||
/** @type {!HTMLVideoElement} */ (document.createElement('video'));
|
||||
videoContainer.appendChild(video);
|
||||
createUIThroughAPI(videoContainer, video);
|
||||
});
|
||||
|
||||
afterAll(function() {
|
||||
document.body.removeChild(videoContainer);
|
||||
});
|
||||
|
||||
it('has all the basic elements', function() {
|
||||
checkBasicUIElements(videoContainer);
|
||||
});
|
||||
});
|
||||
|
||||
describe('constructed through DOM auto-setup', function() {
|
||||
describe('set up with one container', function() {
|
||||
/** @type {!HTMLElement} */
|
||||
let container;
|
||||
|
||||
beforeAll(function() {
|
||||
container =
|
||||
/** @type {!HTMLElement} */ (document.createElement('div'));
|
||||
document.body.appendChild(container);
|
||||
|
||||
createUIThroughDOMAutoSetup([container], /* videos */ []);
|
||||
});
|
||||
|
||||
afterAll(function() {
|
||||
document.body.removeChild(container);
|
||||
});
|
||||
|
||||
it('has all the basic elements', function() {
|
||||
checkBasicUIElements(container);
|
||||
});
|
||||
});
|
||||
|
||||
describe('set up with several containers', function() {
|
||||
/** @type {!HTMLElement} */
|
||||
let container1;
|
||||
|
||||
/** @type {!HTMLElement} */
|
||||
let container2;
|
||||
|
||||
beforeAll(function() {
|
||||
container1 =
|
||||
/** @type {!HTMLElement} */ (document.createElement('div'));
|
||||
document.body.appendChild(container1);
|
||||
|
||||
container2 =
|
||||
/** @type {!HTMLElement} */ (document.createElement('div'));
|
||||
document.body.appendChild(container2);
|
||||
|
||||
createUIThroughDOMAutoSetup([container1, container2], /* videos */ []);
|
||||
});
|
||||
|
||||
afterAll(function() {
|
||||
document.body.removeChild(container1);
|
||||
document.body.removeChild(container2);
|
||||
});
|
||||
|
||||
it('has all the basic elements', function() {
|
||||
checkBasicUIElements(container1);
|
||||
checkBasicUIElements(container2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('set up with one video', function() {
|
||||
/** @type {!HTMLVideoElement} */
|
||||
let video;
|
||||
|
||||
beforeAll(function() {
|
||||
video =
|
||||
/** @type {!HTMLVideoElement} */ (document.createElement('video'));
|
||||
document.body.appendChild(video);
|
||||
|
||||
createUIThroughDOMAutoSetup(/* containers */ [], [video]);
|
||||
});
|
||||
|
||||
afterAll(function() {
|
||||
// createUIThroughDOMAutoSetup will add a div between body and the video
|
||||
document.body.removeChild(video.parentElement);
|
||||
});
|
||||
|
||||
it('has all the basic elements', function() {
|
||||
checkBasicUIElements(
|
||||
/** @type {!HTMLVideoElement} */ (video.parentElement));
|
||||
});
|
||||
});
|
||||
|
||||
describe('set up with several videos', function() {
|
||||
/** @type {!Array.<!HTMLVideoElement>} */
|
||||
let videos = [];
|
||||
|
||||
beforeAll(function() {
|
||||
// Four is just a random number I (ismena) came up with to test a
|
||||
// multi-video use case. It could be replaces with any other
|
||||
// (reasonable) number.
|
||||
for (let i = 0; i < 4; i++) {
|
||||
let video = /** @type {!HTMLVideoElement} */
|
||||
(document.createElement('video'));
|
||||
|
||||
document.body.appendChild(video);
|
||||
videos.push(video);
|
||||
}
|
||||
|
||||
createUIThroughDOMAutoSetup(/* containers */ [], videos);
|
||||
});
|
||||
|
||||
afterAll(function() {
|
||||
videos.forEach(function(video) {
|
||||
document.body.removeChild(video.parentElement);
|
||||
});
|
||||
});
|
||||
|
||||
it('has all the basic elements', function() {
|
||||
videos.forEach(function(video) {
|
||||
checkBasicUIElements(
|
||||
/** @type {!HTMLVideoElement} */ (video.parentElement));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('set up with a video and a container', function() {
|
||||
/** @type {!HTMLElement} */
|
||||
let container;
|
||||
/** @type {!HTMLVideoElement} */
|
||||
let video;
|
||||
|
||||
beforeAll(function() {
|
||||
container =
|
||||
/** @type {!HTMLElement} */ (document.createElement('div'));
|
||||
document.body.appendChild(container);
|
||||
|
||||
video =
|
||||
/** @type {!HTMLVideoElement} */ (document.createElement('video'));
|
||||
container.appendChild(video);
|
||||
|
||||
createUIThroughDOMAutoSetup([container], [video]);
|
||||
});
|
||||
|
||||
afterAll(function() {
|
||||
document.body.removeChild(container);
|
||||
});
|
||||
|
||||
it('has all the basic elements', function() {
|
||||
checkBasicUIElements(container);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('controls', function() {
|
||||
/** @type {!HTMLElement} */
|
||||
let videoContainer;
|
||||
/** @type {!HTMLVideoElement} */
|
||||
let video;
|
||||
|
||||
beforeEach(function() {
|
||||
videoContainer =
|
||||
/** @type {!HTMLElement} */ (document.createElement('div'));
|
||||
document.body.appendChild(videoContainer);
|
||||
|
||||
video =
|
||||
/** @type {!HTMLVideoElement} */ (document.createElement('video'));
|
||||
videoContainer.appendChild(video);
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
document.body.removeChild(videoContainer);
|
||||
});
|
||||
|
||||
describe('all the controls', function() {
|
||||
/** @type {!HTMLElement} */
|
||||
let controlsContainer;
|
||||
|
||||
beforeEach(function() {
|
||||
createUIThroughAPI(videoContainer, video);
|
||||
let controlsContainers =
|
||||
videoContainer.getElementsByClassName('shaka-controls-container');
|
||||
expect(controlsContainers.length).toBe(1);
|
||||
controlsContainer = /** @type {!HTMLElement} */ (controlsContainers[0]);
|
||||
});
|
||||
|
||||
it('stay visible if overflow menuButton is open', function() {
|
||||
let overflowMenus =
|
||||
videoContainer.getElementsByClassName('shaka-overflow-menu');
|
||||
expect(overflowMenus.length).toBe(1);
|
||||
let overflowMenu = /** @type {!HTMLElement} */ (overflowMenus[0]);
|
||||
|
||||
let overflowMenuButtons =
|
||||
videoContainer.getElementsByClassName('shaka-overflow-menu-button');
|
||||
expect(overflowMenuButtons.length).toBe(1);
|
||||
let overflowMenuButton = overflowMenuButtons[0];
|
||||
|
||||
overflowMenuButton.click();
|
||||
expect(overflowMenu.style.display).not.toEqual('none');
|
||||
expect(controlsContainer.style.display).not.toEqual('none');
|
||||
});
|
||||
});
|
||||
|
||||
describe('overflow menu', function() {
|
||||
/** @type {!HTMLElement} */
|
||||
let overflowMenu;
|
||||
|
||||
beforeEach(function() {
|
||||
let config = {
|
||||
controlPanelElements: [
|
||||
'overflow_menu',
|
||||
],
|
||||
};
|
||||
createUIThroughAPI(videoContainer, video, config);
|
||||
|
||||
let overflowMenus =
|
||||
videoContainer.getElementsByClassName('shaka-overflow-menu');
|
||||
expect(overflowMenus.length).toBe(1);
|
||||
overflowMenu = /** @type {!HTMLElement} */ (overflowMenus[0]);
|
||||
});
|
||||
|
||||
it('has default buttons', function() {
|
||||
let captionButtons =
|
||||
overflowMenu.getElementsByClassName('shaka-caption-button');
|
||||
expect(captionButtons.length).toBe(1);
|
||||
});
|
||||
|
||||
it('becomes visible if overflowMenuButton was clicked', function() {
|
||||
let display = window.getComputedStyle(overflowMenu, null).display;
|
||||
expect(display).toEqual('none');
|
||||
|
||||
let overflowMenuButtons =
|
||||
videoContainer.getElementsByClassName('shaka-overflow-menu-button');
|
||||
expect(overflowMenuButtons.length).toBe(1);
|
||||
let overflowMenuButton = overflowMenuButtons[0];
|
||||
|
||||
overflowMenuButton.click();
|
||||
display = overflowMenu.style.display;
|
||||
expect(display).not.toEqual('none');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('controls-button-panel', function() {
|
||||
/** @type {!HTMLElement} */
|
||||
let controlsButtonPanel;
|
||||
|
||||
beforeEach(function() {
|
||||
createUIThroughAPI(videoContainer, video);
|
||||
let controlsButtonPanels = videoContainer.getElementsByClassName(
|
||||
'shaka-controls-button-panel');
|
||||
expect(controlsButtonPanels.length).toBe(1);
|
||||
controlsButtonPanel =
|
||||
/** @type {!HTMLElement} */ (controlsButtonPanels[0]);
|
||||
});
|
||||
|
||||
it('has default elements', function() {
|
||||
let currentTimes =
|
||||
controlsButtonPanel.getElementsByClassName('shaka-time-container');
|
||||
expect(currentTimes.length).toBe(1);
|
||||
|
||||
let shakaMuteButtons =
|
||||
controlsButtonPanel.getElementsByClassName('shaka-mute-button');
|
||||
expect(shakaMuteButtons.length).toBe(1);
|
||||
|
||||
let volumeBars =
|
||||
controlsButtonPanel.getElementsByClassName('shaka-volume-bar');
|
||||
expect(volumeBars.length).toBe(1);
|
||||
|
||||
let fullscreenButtons =
|
||||
controlsButtonPanel.getElementsByClassName('shaka-fullscreen-button');
|
||||
expect(fullscreenButtons.length).toBe(1);
|
||||
|
||||
let overflowMenuButtons = controlsButtonPanel.getElementsByClassName(
|
||||
'shaka-overflow-menu-button');
|
||||
expect(overflowMenuButtons.length).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('resolutions menu', function() {
|
||||
/** @type {!HTMLElement} */
|
||||
let resolutionsMenu;
|
||||
|
||||
beforeEach(function() {
|
||||
let config = {
|
||||
controlPanelElements: [
|
||||
'overflow_menu',
|
||||
],
|
||||
overflowMenuButtons: [
|
||||
'quality',
|
||||
],
|
||||
};
|
||||
createUIThroughAPI(videoContainer, video, config);
|
||||
|
||||
let resolutionsMenus =
|
||||
videoContainer.getElementsByClassName('shaka-resolutions');
|
||||
expect(resolutionsMenus.length).toBe(1);
|
||||
resolutionsMenu = /** @type {!HTMLElement} */ (resolutionsMenus[0]);
|
||||
});
|
||||
|
||||
it('becomes visible if resolutionButton was clicked', function() {
|
||||
let display = window.getComputedStyle(resolutionsMenu, null).display;
|
||||
expect(display).toEqual('none');
|
||||
|
||||
let resolutionButtons =
|
||||
videoContainer.getElementsByClassName('shaka-resolution-button');
|
||||
expect(resolutionButtons.length).toBe(1);
|
||||
let resolutionButton = resolutionButtons[0];
|
||||
|
||||
resolutionButton.click();
|
||||
display = resolutionsMenu.style.display;
|
||||
expect(display).not.toEqual('none');
|
||||
});
|
||||
|
||||
it('clears the buffer when changing resolutions', async () => {
|
||||
// Load fake content that has more than one quality level.
|
||||
const manifest = new shaka.test.ManifestGenerator()
|
||||
.addPeriod(0)
|
||||
.addVariant(0)
|
||||
.addVideo(1).size(320, 240)
|
||||
.addVideo(2).size(640, 480)
|
||||
.build();
|
||||
|
||||
const parser = new shaka.test.FakeManifestParser(manifest);
|
||||
const factory = function() { return parser; };
|
||||
|
||||
await player.load(/* uri */ 'fake', /* startTime */ 0, factory);
|
||||
|
||||
const selectVariantTrack = spyOn(player, 'selectVariantTrack');
|
||||
|
||||
// There should be at least one explicit quality button.
|
||||
const qualityButton =
|
||||
videoContainer.querySelectorAll('button.explicit-resolution')[0];
|
||||
expect(qualityButton).toBeDefined();
|
||||
|
||||
// Clicking this should select a track and clear the buffer.
|
||||
expect(selectVariantTrack).not.toHaveBeenCalled();
|
||||
qualityButton.click();
|
||||
|
||||
// The second argument is "clearBuffer", and should be true.
|
||||
expect(selectVariantTrack).toHaveBeenCalledWith(
|
||||
jasmine.any(Object), true);
|
||||
});
|
||||
|
||||
// TODO: integration test to ensure all resolutions are
|
||||
// correctly represented
|
||||
});
|
||||
|
||||
// TODO: integration test to test audio language menu.
|
||||
});
|
||||
|
||||
describe('customization', function() {
|
||||
/** @type {!HTMLElement} */
|
||||
let container;
|
||||
/** @type {!HTMLMediaElement} */
|
||||
let video;
|
||||
/** @type {!Object} */
|
||||
let config;
|
||||
|
||||
let warning;
|
||||
let originalWarning;
|
||||
|
||||
beforeEach(function() {
|
||||
originalWarning = shaka.log.warning;
|
||||
warning = jasmine.createSpy('shaka.log.warning');
|
||||
|
||||
shaka.log.warning = shaka.test.Util.spyFunc(warning);
|
||||
warning.calls.reset();
|
||||
container =
|
||||
/** @type {!HTMLElement} */ (document.createElement('div'));
|
||||
document.body.appendChild(container);
|
||||
|
||||
video =
|
||||
/** @type {!HTMLVideoElement} */ (document.createElement('video'));
|
||||
container.appendChild(video);
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
document.body.removeChild(container);
|
||||
shaka.log.warning = originalWarning;
|
||||
});
|
||||
|
||||
it('only the specified controls are created', function() {
|
||||
config = {controlPanelElements: ['time_and_duration', 'mute']};
|
||||
createUIThroughAPI(container, video, config);
|
||||
|
||||
// Only current time and mute button should've been created
|
||||
let currentTimes =
|
||||
container.getElementsByClassName('shaka-time-container');
|
||||
expect(currentTimes.length).toBe(1);
|
||||
|
||||
let shakaMuteButtons =
|
||||
container.getElementsByClassName('shaka-mute-button');
|
||||
expect(shakaMuteButtons.length).toBe(1);
|
||||
|
||||
let volumeBars =
|
||||
container.getElementsByClassName('shaka-volume-bar');
|
||||
expect(volumeBars.length).toBe(0);
|
||||
|
||||
let fullscreenButtons =
|
||||
container.getElementsByClassName('fullscreenButton');
|
||||
expect(fullscreenButtons.length).toBe(0);
|
||||
|
||||
let overflowMenuButtons =
|
||||
container.getElementsByClassName('shaka-overflow-menu-button');
|
||||
expect(overflowMenuButtons.length).toBe(0);
|
||||
});
|
||||
|
||||
it('only the specified overflow menu buttons are created', function() {
|
||||
config = {overflowMenuButtons: ['cast']};
|
||||
createUIThroughAPI(container, video, config);
|
||||
|
||||
let castButtons =
|
||||
container.getElementsByClassName('shaka-cast-button');
|
||||
expect(castButtons.length).toBe(1);
|
||||
|
||||
let captionButtons =
|
||||
container.getElementsByClassName('shaka-caption-button');
|
||||
expect(captionButtons.length).toBe(0);
|
||||
});
|
||||
|
||||
it('overlfow menu elements are not created in control button panel',
|
||||
function() {
|
||||
expect(warning).not.toHaveBeenCalled();
|
||||
config = {controlPanelElements: ['cast']};
|
||||
createUIThroughAPI(container, video, config);
|
||||
|
||||
// We do not provide captions button as part of controls button panel,
|
||||
// only in overflow menu
|
||||
let castButtons =
|
||||
container.getElementsByClassName('shaka-cast-button');
|
||||
expect(castButtons.length).toBe(0);
|
||||
expect(warning).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('control button panel elements are not created in overlfow menu',
|
||||
function() {
|
||||
expect(warning).not.toHaveBeenCalled();
|
||||
config = {overflowMenuButtons: ['rewind']};
|
||||
createUIThroughAPI(container, video, config);
|
||||
|
||||
// We do not provide captions button as part of controls button panel,
|
||||
// only in overflow menu
|
||||
let rewindButtons =
|
||||
container.getElementsByClassName('shaka-rewind-button');
|
||||
expect(rewindButtons.length).toBe(0);
|
||||
expect(warning).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('seek bar is not created unless configured', function() {
|
||||
config = {addSeekBar: false};
|
||||
createUIThroughAPI(container, video, config);
|
||||
|
||||
let seekBars =
|
||||
container.getElementsByClassName('shaka-seek-bar');
|
||||
expect(seekBars.length).toBe(0);
|
||||
});
|
||||
|
||||
it('seek bar is created when configured', function() {
|
||||
config = {addSeekBar: true};
|
||||
createUIThroughAPI(container, video, config);
|
||||
|
||||
let seekBars =
|
||||
container.getElementsByClassName('shaka-seek-bar');
|
||||
expect(seekBars.length).toBe(1);
|
||||
});
|
||||
|
||||
it('settings menus are positioned lower when seek bar is absent',
|
||||
function() {
|
||||
config = {addSeekBar: false};
|
||||
createUIThroughAPI(container, video, config);
|
||||
|
||||
let seekBars =
|
||||
container.getElementsByClassName('shaka-seek-bar');
|
||||
expect(seekBars.length).toBe(0);
|
||||
|
||||
let overflowMenus =
|
||||
container.getElementsByClassName('shaka-overflow-menu');
|
||||
expect(overflowMenus.length).toBe(1);
|
||||
expect(overflowMenus[0].classList.contains(
|
||||
'shaka-low-position')).toBe(true);
|
||||
|
||||
let resolutionMenus =
|
||||
container.getElementsByClassName('shaka-resolutions');
|
||||
expect(resolutionMenus.length).toBe(1);
|
||||
expect(resolutionMenus[0].classList.contains(
|
||||
'shaka-low-position')).toBe(true);
|
||||
|
||||
let audioLangMenus =
|
||||
container.getElementsByClassName('shaka-audio-languages');
|
||||
expect(audioLangMenus.length).toBe(1);
|
||||
expect(audioLangMenus[0].classList.contains(
|
||||
'shaka-low-position')).toBe(true);
|
||||
|
||||
let textLangMenus =
|
||||
container.getElementsByClassName('shaka-text-languages');
|
||||
expect(textLangMenus.length).toBe(1);
|
||||
expect(textLangMenus[0].classList.contains(
|
||||
'shaka-low-position')).toBe(true);
|
||||
});
|
||||
|
||||
it('controls are created in specified order', function() {
|
||||
config = {controlPanelElements: ['mute', 'time_and_duration',
|
||||
'fullscreen']};
|
||||
createUIThroughAPI(container, video, config);
|
||||
|
||||
let controlsButtonPanels =
|
||||
container.getElementsByClassName('shaka-controls-button-panel');
|
||||
expect(controlsButtonPanels.length).toBe(1);
|
||||
let controlsButtonPanel =
|
||||
/** @type {!HTMLElement} */ (controlsButtonPanels[0]);
|
||||
|
||||
let buttons = controlsButtonPanel.childNodes;
|
||||
expect(buttons.length).toBe(3);
|
||||
|
||||
expect( /** @type {!HTMLElement} */ (buttons[0]).className)
|
||||
.toContain('shaka-mute-button');
|
||||
expect( /** @type {!HTMLElement} */ (buttons[1]).className)
|
||||
.toContain('shaka-time-container');
|
||||
expect( /** @type {!HTMLElement} */ (buttons[2]).className)
|
||||
.toContain('shaka-fullscreen');
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* @param {!HTMLElement} container
|
||||
* @suppress {visibility}
|
||||
*/
|
||||
function checkBasicUIElements(container) {
|
||||
let videos = container.getElementsByTagName('video');
|
||||
expect(videos.length).not.toBe(0);
|
||||
|
||||
let playButtonContainers =
|
||||
container.getElementsByClassName('shaka-play-button-container');
|
||||
expect(playButtonContainers.length).toBe(1);
|
||||
|
||||
let playButtons =
|
||||
container.getElementsByClassName('shaka-play-button');
|
||||
expect(playButtons.length).toBe(1);
|
||||
|
||||
let bufferingSpinners =
|
||||
container.getElementsByClassName('shaka-buffering-spinner');
|
||||
expect(bufferingSpinners.length).toBe(1);
|
||||
|
||||
let overflowMenus =
|
||||
container.getElementsByClassName('shaka-overflow-menu');
|
||||
expect(overflowMenus.length).toBe(1);
|
||||
|
||||
let controlsButtonPanels =
|
||||
container.getElementsByClassName('shaka-controls-button-panel');
|
||||
expect(controlsButtonPanels.length).toBe(1);
|
||||
|
||||
let seekBars =
|
||||
container.getElementsByClassName('shaka-seek-bar');
|
||||
expect(seekBars.length).toBe(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {!HTMLElement} videoContainer
|
||||
* @param {!HTMLMediaElement} video
|
||||
* @param {!Object=} config
|
||||
*/
|
||||
function createUIThroughAPI(videoContainer, video, config) {
|
||||
player = new shaka.Player(video);
|
||||
// Create UI
|
||||
config = config || {};
|
||||
const ui = new shaka.ui.Overlay(player, videoContainer, video, config);
|
||||
|
||||
// The tests we have at the moment will pass without this, but compiler
|
||||
// complained about not using the ui var, and I(ismena) didn't know
|
||||
// any better.
|
||||
ui.setEnabled(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {!Array.<!Element>} containers
|
||||
* @param {!Array.<!Element>} videos
|
||||
* @suppress {visibility}
|
||||
*/
|
||||
function createUIThroughDOMAutoSetup(containers, videos) {
|
||||
containers.forEach(function(container) {
|
||||
container.setAttribute('data-shaka-player-container', '');
|
||||
});
|
||||
|
||||
videos.forEach(function(video) {
|
||||
video.setAttribute('data-shaka-player', '');
|
||||
});
|
||||
|
||||
// Call UI's private method to scan the page for shaka
|
||||
// elements and create the UI.
|
||||
shaka.ui.Overlay.scanPageForShakaElements_();
|
||||
}
|
||||
});
|
||||
|
||||
Vendored
+5
@@ -14,3 +14,8 @@ closure/deps
|
||||
The closure dependency generator, part of the closure library, by Google.
|
||||
Apache v2.0 license.
|
||||
https://github.com/google/closure-library
|
||||
|
||||
language-mapping-list
|
||||
A map of languages to their names, v0.0.16, by Ali Al Dallal / Mozilla.
|
||||
MIT license.
|
||||
https://github.com/mozilla/language-mapping-list
|
||||
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2013 Ali Al Dallal
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
language-mapping-list
|
||||
=====================
|
||||
|
||||
List of all the known languages in their English and Native name with locales.
|
||||
|
||||
There are over 200 languages available in the list.
|
||||
|
||||
`$ npm install langmap`
|
||||
|
||||
Usage:
|
||||
|
||||
```
|
||||
var langmap = require("langmap");
|
||||
|
||||
"Native" => English (US)
|
||||
var native = langmap["en-US"]["nativeName"];
|
||||
"Native" => ภาษาไทย
|
||||
var native = langmap["th"]["nativeName"];
|
||||
"English" => Thai
|
||||
var native = langmap["th"]["englishName"];
|
||||
|
||||
```
|
||||
@@ -0,0 +1,896 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2013 Ali Al Dallal
|
||||
*
|
||||
* Licensed under the MIT license.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
goog.provide('mozilla.LanguageMapping');
|
||||
|
||||
/**
|
||||
* A mapping from language code to the native and English names for that
|
||||
* language. The language code can be specific and include a locale, such as
|
||||
* "es-CL" instead of merely "es".
|
||||
*
|
||||
* @type {!Object.<string, {nativeName: string, englishName: string}>}
|
||||
*/
|
||||
mozilla.LanguageMapping = {
|
||||
'ach': {
|
||||
nativeName: "Lwo",
|
||||
englishName: "Acholi"
|
||||
},
|
||||
'ady': {
|
||||
nativeName: "Адыгэбзэ",
|
||||
englishName: "Adyghe"
|
||||
},
|
||||
'af': {
|
||||
nativeName: "Afrikaans",
|
||||
englishName: "Afrikaans"
|
||||
},
|
||||
'af-NA': {
|
||||
nativeName: "Afrikaans (Namibia)",
|
||||
englishName: "Afrikaans (Namibia)"
|
||||
},
|
||||
'af-ZA': {
|
||||
nativeName: "Afrikaans (South Africa)",
|
||||
englishName: "Afrikaans (South Africa)"
|
||||
},
|
||||
'ak': {
|
||||
nativeName: "Tɕɥi",
|
||||
englishName: "Akan"
|
||||
},
|
||||
'ar': {
|
||||
nativeName: "العربية",
|
||||
englishName: "Arabic"
|
||||
},
|
||||
'ar-AR': {
|
||||
nativeName: "العربية",
|
||||
englishName: "Arabic"
|
||||
},
|
||||
'ar-MA': {
|
||||
nativeName: "العربية",
|
||||
englishName: "Arabic (Morocco)"
|
||||
},
|
||||
'ar-SA': {
|
||||
nativeName: "العربية (السعودية)",
|
||||
englishName: "Arabic (Saudi Arabia)"
|
||||
},
|
||||
'ay-BO': {
|
||||
nativeName: "Aymar aru",
|
||||
englishName: "Aymara"
|
||||
},
|
||||
'az': {
|
||||
nativeName: "Azərbaycan dili",
|
||||
englishName: "Azerbaijani"
|
||||
},
|
||||
'az-AZ': {
|
||||
nativeName: "Azərbaycan dili",
|
||||
englishName: "Azerbaijani"
|
||||
},
|
||||
'be-BY': {
|
||||
nativeName: "Беларуская",
|
||||
englishName: "Belarusian"
|
||||
},
|
||||
'bg': {
|
||||
nativeName: "Български",
|
||||
englishName: "Bulgarian"
|
||||
},
|
||||
'bg-BG': {
|
||||
nativeName: "Български",
|
||||
englishName: "Bulgarian"
|
||||
},
|
||||
'bn': {
|
||||
nativeName: "বাংলা",
|
||||
englishName: "Bengali"
|
||||
},
|
||||
'bn-IN': {
|
||||
nativeName: "বাংলা (ভারত)",
|
||||
englishName: "Bengali (India)"
|
||||
},
|
||||
'bn-BD': {
|
||||
nativeName: "বাংলা(বাংলাদেশ)",
|
||||
englishName: "Bengali (Bangladesh)"
|
||||
},
|
||||
'bs-BA': {
|
||||
nativeName: "Bosanski",
|
||||
englishName: "Bosnian"
|
||||
},
|
||||
'ca': {
|
||||
nativeName: "Català",
|
||||
englishName: "Catalan"
|
||||
},
|
||||
'ca-ES': {
|
||||
nativeName: "Català",
|
||||
englishName: "Catalan"
|
||||
},
|
||||
'cak': {
|
||||
nativeName: "Maya Kaqchikel",
|
||||
englishName: "Kaqchikel"
|
||||
},
|
||||
'ck-US': {
|
||||
nativeName: "ᏣᎳᎩ (tsalagi)",
|
||||
englishName: "Cherokee"
|
||||
},
|
||||
'cs': {
|
||||
nativeName: "Čeština",
|
||||
englishName: "Czech"
|
||||
},
|
||||
'cs-CZ': {
|
||||
nativeName: "Čeština",
|
||||
englishName: "Czech"
|
||||
},
|
||||
'cy': {
|
||||
nativeName: "Cymraeg",
|
||||
englishName: "Welsh"
|
||||
},
|
||||
'cy-GB': {
|
||||
nativeName: "Cymraeg",
|
||||
englishName: "Welsh"
|
||||
},
|
||||
'da': {
|
||||
nativeName: "Dansk",
|
||||
englishName: "Danish"
|
||||
},
|
||||
'da-DK': {
|
||||
nativeName: "Dansk",
|
||||
englishName: "Danish"
|
||||
},
|
||||
'de': {
|
||||
nativeName: "Deutsch",
|
||||
englishName: "German"
|
||||
},
|
||||
'de-AT': {
|
||||
nativeName: "Deutsch (Österreich)",
|
||||
englishName: "German (Austria)"
|
||||
},
|
||||
'de-DE': {
|
||||
nativeName: "Deutsch (Deutschland)",
|
||||
englishName: "German (Germany)"
|
||||
},
|
||||
'de-CH': {
|
||||
nativeName: "Deutsch (Schweiz)",
|
||||
englishName: "German (Switzerland)"
|
||||
},
|
||||
'dsb': {
|
||||
nativeName: "Dolnoserbšćina",
|
||||
englishName: "Lower Sorbian"
|
||||
},
|
||||
'el': {
|
||||
nativeName: "Ελληνικά",
|
||||
englishName: "Greek"
|
||||
},
|
||||
'el-GR': {
|
||||
nativeName: "Ελληνικά",
|
||||
englishName: "Greek (Greece)"
|
||||
},
|
||||
'en': {
|
||||
nativeName: "English",
|
||||
englishName: "English"
|
||||
},
|
||||
'en-GB': {
|
||||
nativeName: "English (UK)",
|
||||
englishName: "English (UK)"
|
||||
},
|
||||
'en-AU': {
|
||||
nativeName: "English (Australia)",
|
||||
englishName: "English (Australia)"
|
||||
},
|
||||
'en-CA': {
|
||||
nativeName: "English (Canada)",
|
||||
englishName: "English (Canada)"
|
||||
},
|
||||
'en-IE': {
|
||||
nativeName: "English (Ireland)",
|
||||
englishName: "English (Ireland)"
|
||||
},
|
||||
'en-IN': {
|
||||
nativeName: "English (India)",
|
||||
englishName: "English (India)"
|
||||
},
|
||||
'en-PI': {
|
||||
nativeName: "English (Pirate)",
|
||||
englishName: "English (Pirate)"
|
||||
},
|
||||
'en-UD': {
|
||||
nativeName: "English (Upside Down)",
|
||||
englishName: "English (Upside Down)"
|
||||
},
|
||||
'en-US': {
|
||||
nativeName: "English (US)",
|
||||
englishName: "English (US)"
|
||||
},
|
||||
'en-ZA': {
|
||||
nativeName: "English (South Africa)",
|
||||
englishName: "English (South Africa)"
|
||||
},
|
||||
'en@pirate': {
|
||||
nativeName: "English (Pirate)",
|
||||
englishName: "English (Pirate)"
|
||||
},
|
||||
'eo': {
|
||||
nativeName: "Esperanto",
|
||||
englishName: "Esperanto"
|
||||
},
|
||||
'eo-EO': {
|
||||
nativeName: "Esperanto",
|
||||
englishName: "Esperanto"
|
||||
},
|
||||
'es': {
|
||||
nativeName: "Español",
|
||||
englishName: "Spanish"
|
||||
},
|
||||
'es-AR': {
|
||||
nativeName: "Español (Argentine)",
|
||||
englishName: "Spanish (Argentina)"
|
||||
},
|
||||
'es-419': {
|
||||
nativeName: "Español (Latinoamérica)",
|
||||
englishName: "Spanish (Latin America)"
|
||||
},
|
||||
'es-CL': {
|
||||
nativeName: "Español (Chile)",
|
||||
englishName: "Spanish (Chile)"
|
||||
},
|
||||
'es-CO': {
|
||||
nativeName: "Español (Colombia)",
|
||||
englishName: "Spanish (Colombia)"
|
||||
},
|
||||
'es-EC': {
|
||||
nativeName: "Español (Ecuador)",
|
||||
englishName: "Spanish (Ecuador)"
|
||||
},
|
||||
'es-ES': {
|
||||
nativeName: "Español (España)",
|
||||
englishName: "Spanish (Spain)"
|
||||
},
|
||||
'es-LA': {
|
||||
nativeName: "Español (Latinoamérica)",
|
||||
englishName: "Spanish (Latin America)"
|
||||
},
|
||||
'es-NI': {
|
||||
nativeName: "Español (Nicaragua)",
|
||||
englishName: "Spanish (Nicaragua)"
|
||||
},
|
||||
'es-MX': {
|
||||
nativeName: "Español (México)",
|
||||
englishName: "Spanish (Mexico)"
|
||||
},
|
||||
'es-US': {
|
||||
nativeName: "Español (Estados Unidos)",
|
||||
englishName: "Spanish (United States)"
|
||||
},
|
||||
'es-VE': {
|
||||
nativeName: "Español (Venezuela)",
|
||||
englishName: "Spanish (Venezuela)"
|
||||
},
|
||||
'et': {
|
||||
nativeName: "eesti keel",
|
||||
englishName: "Estonian"
|
||||
},
|
||||
'et-EE': {
|
||||
nativeName: "Eesti (Estonia)",
|
||||
englishName: "Estonian (Estonia)"
|
||||
},
|
||||
'eu': {
|
||||
nativeName: "Euskara",
|
||||
englishName: "Basque"
|
||||
},
|
||||
'eu-ES': {
|
||||
nativeName: "Euskara",
|
||||
englishName: "Basque"
|
||||
},
|
||||
'fa': {
|
||||
nativeName: "فارسی",
|
||||
englishName: "Persian"
|
||||
},
|
||||
'fa-IR': {
|
||||
nativeName: "فارسی",
|
||||
englishName: "Persian"
|
||||
},
|
||||
'fb-LT': {
|
||||
nativeName: "Leet Speak",
|
||||
englishName: "Leet"
|
||||
},
|
||||
'ff': {
|
||||
nativeName: "Fulah",
|
||||
englishName: "Fulah"
|
||||
},
|
||||
'fi': {
|
||||
nativeName: "Suomi",
|
||||
englishName: "Finnish"
|
||||
},
|
||||
'fi-FI': {
|
||||
nativeName: "Suomi",
|
||||
englishName: "Finnish"
|
||||
},
|
||||
'fo-FO': {
|
||||
nativeName: "Føroyskt",
|
||||
englishName: "Faroese"
|
||||
},
|
||||
'fr': {
|
||||
nativeName: "Français",
|
||||
englishName: "French"
|
||||
},
|
||||
'fr-CA': {
|
||||
nativeName: "Français (Canada)",
|
||||
englishName: "French (Canada)"
|
||||
},
|
||||
'fr-FR': {
|
||||
nativeName: "Français (France)",
|
||||
englishName: "French (France)"
|
||||
},
|
||||
'fr-BE': {
|
||||
nativeName: "Français (Belgique)",
|
||||
englishName: "French (Belgium)"
|
||||
},
|
||||
'fr-CH': {
|
||||
nativeName: "Français (Suisse)",
|
||||
englishName: "French (Switzerland)"
|
||||
},
|
||||
'fy-NL': {
|
||||
nativeName: "Frysk",
|
||||
englishName: "Frisian (West)"
|
||||
},
|
||||
'ga': {
|
||||
nativeName: "Gaeilge",
|
||||
englishName: "Irish"
|
||||
},
|
||||
'ga-IE': {
|
||||
nativeName: "Gaeilge (Gaelic)",
|
||||
englishName: "Irish (Gaelic)"
|
||||
},
|
||||
'gl': {
|
||||
nativeName: "Galego",
|
||||
englishName: "Galician"
|
||||
},
|
||||
'gl-ES': {
|
||||
nativeName: "Galego",
|
||||
englishName: "Galician"
|
||||
},
|
||||
'gn-PY': {
|
||||
nativeName: "Avañe'ẽ",
|
||||
englishName: "Guarani"
|
||||
},
|
||||
'gu-IN': {
|
||||
nativeName: "ગુજરાતી",
|
||||
englishName: "Gujarati"
|
||||
},
|
||||
'gx-GR': {
|
||||
nativeName: "Ἑλληνική ἀρχαία",
|
||||
englishName: "Classical Greek"
|
||||
},
|
||||
'he': {
|
||||
nativeName: "עברית",
|
||||
englishName: "Hebrew"
|
||||
},
|
||||
'he-IL': {
|
||||
nativeName: "עברית",
|
||||
englishName: "Hebrew"
|
||||
},
|
||||
'hi': {
|
||||
nativeName: "हिन्दी",
|
||||
englishName: "Hindi"
|
||||
},
|
||||
'hi-IN': {
|
||||
nativeName: "हिन्दी",
|
||||
englishName: "Hindi"
|
||||
},
|
||||
'hr': {
|
||||
nativeName: "Hrvatski",
|
||||
englishName: "Croatian"
|
||||
},
|
||||
'hr-HR': {
|
||||
nativeName: "Hrvatski",
|
||||
englishName: "Croatian"
|
||||
},
|
||||
'hsb': {
|
||||
nativeName: "Hornjoserbšćina",
|
||||
englishName: "Upper Sorbian"
|
||||
},
|
||||
'ht': {
|
||||
nativeName: "Kreyòl",
|
||||
englishName: "Haitian Creole"
|
||||
},
|
||||
'hu': {
|
||||
nativeName: "Magyar",
|
||||
englishName: "Hungarian"
|
||||
},
|
||||
'hu-HU': {
|
||||
nativeName: "Magyar",
|
||||
englishName: "Hungarian"
|
||||
},
|
||||
'hy-AM': {
|
||||
nativeName: "Հայերեն",
|
||||
englishName: "Armenian"
|
||||
},
|
||||
'id': {
|
||||
nativeName: "Bahasa Indonesia",
|
||||
englishName: "Indonesian"
|
||||
},
|
||||
'id-ID': {
|
||||
nativeName: "Bahasa Indonesia",
|
||||
englishName: "Indonesian"
|
||||
},
|
||||
'is': {
|
||||
nativeName: "Íslenska",
|
||||
englishName: "Icelandic"
|
||||
},
|
||||
'is-IS': {
|
||||
nativeName: "Íslenska (Iceland)",
|
||||
englishName: "Icelandic (Iceland)"
|
||||
},
|
||||
'it': {
|
||||
nativeName: "Italiano",
|
||||
englishName: "Italian"
|
||||
},
|
||||
'it-IT': {
|
||||
nativeName: "Italiano",
|
||||
englishName: "Italian"
|
||||
},
|
||||
'ja': {
|
||||
nativeName: "日本語",
|
||||
englishName: "Japanese"
|
||||
},
|
||||
'ja-JP': {
|
||||
nativeName: "日本語",
|
||||
englishName: "Japanese"
|
||||
},
|
||||
'jv-ID': {
|
||||
nativeName: "Basa Jawa",
|
||||
englishName: "Javanese"
|
||||
},
|
||||
'ka-GE': {
|
||||
nativeName: "ქართული",
|
||||
englishName: "Georgian"
|
||||
},
|
||||
'kk-KZ': {
|
||||
nativeName: "Қазақша",
|
||||
englishName: "Kazakh"
|
||||
},
|
||||
'km': {
|
||||
nativeName: "ភាសាខ្មែរ",
|
||||
englishName: "Khmer"
|
||||
},
|
||||
'km-KH': {
|
||||
nativeName: "ភាសាខ្មែរ",
|
||||
englishName: "Khmer"
|
||||
},
|
||||
'kab': {
|
||||
nativeName: "Taqbaylit",
|
||||
englishName: "Kabyle"
|
||||
},
|
||||
'kn': {
|
||||
nativeName: "ಕನ್ನಡ",
|
||||
englishName: "Kannada"
|
||||
},
|
||||
'kn-IN': {
|
||||
nativeName: "ಕನ್ನಡ (India)",
|
||||
englishName: "Kannada (India)"
|
||||
},
|
||||
'ko': {
|
||||
nativeName: "한국어",
|
||||
englishName: "Korean"
|
||||
},
|
||||
'ko-KR': {
|
||||
nativeName: "한국어 (韩国)",
|
||||
englishName: "Korean (Korea)"
|
||||
},
|
||||
'ku-TR': {
|
||||
nativeName: "Kurdî",
|
||||
englishName: "Kurdish"
|
||||
},
|
||||
'la': {
|
||||
nativeName: "Latin",
|
||||
englishName: "Latin"
|
||||
},
|
||||
'la-VA': {
|
||||
nativeName: "Latin",
|
||||
englishName: "Latin"
|
||||
},
|
||||
'lb': {
|
||||
nativeName: "Lëtzebuergesch",
|
||||
englishName: "Luxembourgish"
|
||||
},
|
||||
'li-NL': {
|
||||
nativeName: "Lèmbörgs",
|
||||
englishName: "Limburgish"
|
||||
},
|
||||
'lt': {
|
||||
nativeName: "Lietuvių",
|
||||
englishName: "Lithuanian"
|
||||
},
|
||||
'lt-LT': {
|
||||
nativeName: "Lietuvių",
|
||||
englishName: "Lithuanian"
|
||||
},
|
||||
'lv': {
|
||||
nativeName: "Latviešu",
|
||||
englishName: "Latvian"
|
||||
},
|
||||
'lv-LV': {
|
||||
nativeName: "Latviešu",
|
||||
englishName: "Latvian"
|
||||
},
|
||||
'mai': {
|
||||
nativeName: "मैथिली, মৈথিলী",
|
||||
englishName: "Maithili"
|
||||
},
|
||||
'mg-MG': {
|
||||
nativeName: "Malagasy",
|
||||
englishName: "Malagasy"
|
||||
},
|
||||
'mk': {
|
||||
nativeName: "Македонски",
|
||||
englishName: "Macedonian"
|
||||
},
|
||||
'mk-MK': {
|
||||
nativeName: "Македонски (Македонски)",
|
||||
englishName: "Macedonian (Macedonian)"
|
||||
},
|
||||
'ml': {
|
||||
nativeName: "മലയാളം",
|
||||
englishName: "Malayalam"
|
||||
},
|
||||
'ml-IN': {
|
||||
nativeName: "മലയാളം",
|
||||
englishName: "Malayalam"
|
||||
},
|
||||
'mn-MN': {
|
||||
nativeName: "Монгол",
|
||||
englishName: "Mongolian"
|
||||
},
|
||||
'mr': {
|
||||
nativeName: "मराठी",
|
||||
englishName: "Marathi"
|
||||
},
|
||||
'mr-IN': {
|
||||
nativeName: "मराठी",
|
||||
englishName: "Marathi"
|
||||
},
|
||||
'ms': {
|
||||
nativeName: "Bahasa Melayu",
|
||||
englishName: "Malay"
|
||||
},
|
||||
'ms-MY': {
|
||||
nativeName: "Bahasa Melayu",
|
||||
englishName: "Malay"
|
||||
},
|
||||
'mt': {
|
||||
nativeName: "Malti",
|
||||
englishName: "Maltese"
|
||||
},
|
||||
'mt-MT': {
|
||||
nativeName: "Malti",
|
||||
englishName: "Maltese"
|
||||
},
|
||||
'my': {
|
||||
nativeName: "ဗမာစကာ",
|
||||
englishName: "Burmese"
|
||||
},
|
||||
'no': {
|
||||
nativeName: "Norsk",
|
||||
englishName: "Norwegian"
|
||||
},
|
||||
'nb': {
|
||||
nativeName: "Norsk (bokmål)",
|
||||
englishName: "Norwegian (bokmal)"
|
||||
},
|
||||
'nb-NO': {
|
||||
nativeName: "Norsk (bokmål)",
|
||||
englishName: "Norwegian (bokmal)"
|
||||
},
|
||||
'ne': {
|
||||
nativeName: "नेपाली",
|
||||
englishName: "Nepali"
|
||||
},
|
||||
'ne-NP': {
|
||||
nativeName: "नेपाली",
|
||||
englishName: "Nepali"
|
||||
},
|
||||
'nl': {
|
||||
nativeName: "Nederlands",
|
||||
englishName: "Dutch"
|
||||
},
|
||||
'nl-BE': {
|
||||
nativeName: "Nederlands (België)",
|
||||
englishName: "Dutch (Belgium)"
|
||||
},
|
||||
'nl-NL': {
|
||||
nativeName: "Nederlands (Nederland)",
|
||||
englishName: "Dutch (Netherlands)"
|
||||
},
|
||||
'nn-NO': {
|
||||
nativeName: "Norsk (nynorsk)",
|
||||
englishName: "Norwegian (nynorsk)"
|
||||
},
|
||||
'oc': {
|
||||
nativeName: "Occitan",
|
||||
englishName: "Occitan"
|
||||
},
|
||||
'or-IN': {
|
||||
nativeName: "ଓଡ଼ିଆ",
|
||||
englishName: "Oriya"
|
||||
},
|
||||
'pa': {
|
||||
nativeName: "ਪੰਜਾਬੀ",
|
||||
englishName: "Punjabi"
|
||||
},
|
||||
'pa-IN': {
|
||||
nativeName: "ਪੰਜਾਬੀ (ਭਾਰਤ ਨੂੰ)",
|
||||
englishName: "Punjabi (India)"
|
||||
},
|
||||
'pl': {
|
||||
nativeName: "Polski",
|
||||
englishName: "Polish"
|
||||
},
|
||||
'pl-PL': {
|
||||
nativeName: "Polski",
|
||||
englishName: "Polish"
|
||||
},
|
||||
'ps-AF': {
|
||||
nativeName: "پښتو",
|
||||
englishName: "Pashto"
|
||||
},
|
||||
'pt': {
|
||||
nativeName: "Português",
|
||||
englishName: "Portuguese"
|
||||
},
|
||||
'pt-BR': {
|
||||
nativeName: "Português (Brasil)",
|
||||
englishName: "Portuguese (Brazil)"
|
||||
},
|
||||
'pt-PT': {
|
||||
nativeName: "Português (Portugal)",
|
||||
englishName: "Portuguese (Portugal)"
|
||||
},
|
||||
'qu-PE': {
|
||||
nativeName: "Qhichwa",
|
||||
englishName: "Quechua"
|
||||
},
|
||||
'rm-CH': {
|
||||
nativeName: "Rumantsch",
|
||||
englishName: "Romansh"
|
||||
},
|
||||
'ro': {
|
||||
nativeName: "Română",
|
||||
englishName: "Romanian"
|
||||
},
|
||||
'ro-RO': {
|
||||
nativeName: "Română",
|
||||
englishName: "Romanian"
|
||||
},
|
||||
'ru': {
|
||||
nativeName: "Русский",
|
||||
englishName: "Russian"
|
||||
},
|
||||
'ru-RU': {
|
||||
nativeName: "Русский",
|
||||
englishName: "Russian"
|
||||
},
|
||||
'sa-IN': {
|
||||
nativeName: "संस्कृतम्",
|
||||
englishName: "Sanskrit"
|
||||
},
|
||||
'se-NO': {
|
||||
nativeName: "Davvisámegiella",
|
||||
englishName: "Northern Sámi"
|
||||
},
|
||||
'si-LK': {
|
||||
nativeName: "පළාත",
|
||||
englishName: "Sinhala (Sri Lanka)"
|
||||
},
|
||||
'sk': {
|
||||
nativeName: "Slovenčina",
|
||||
englishName: "Slovak"
|
||||
},
|
||||
'sk-SK': {
|
||||
nativeName: "Slovenčina (Slovakia)",
|
||||
englishName: "Slovak (Slovakia)"
|
||||
},
|
||||
'sl': {
|
||||
nativeName: "Slovenščina",
|
||||
englishName: "Slovenian"
|
||||
},
|
||||
'sl-SI': {
|
||||
nativeName: "Slovenščina",
|
||||
englishName: "Slovenian"
|
||||
},
|
||||
'so-SO': {
|
||||
nativeName: "Soomaaliga",
|
||||
englishName: "Somali"
|
||||
},
|
||||
'sq': {
|
||||
nativeName: "Shqip",
|
||||
englishName: "Albanian"
|
||||
},
|
||||
'sq-AL': {
|
||||
nativeName: "Shqip",
|
||||
englishName: "Albanian"
|
||||
},
|
||||
'sr': {
|
||||
nativeName: "Српски",
|
||||
englishName: "Serbian"
|
||||
},
|
||||
'sr-RS': {
|
||||
nativeName: "Српски (Serbia)",
|
||||
englishName: "Serbian (Serbia)"
|
||||
},
|
||||
'su': {
|
||||
nativeName: "Basa Sunda",
|
||||
englishName: "Sundanese"
|
||||
},
|
||||
'sv': {
|
||||
nativeName: "Svenska",
|
||||
englishName: "Swedish"
|
||||
},
|
||||
'sv-SE': {
|
||||
nativeName: "Svenska",
|
||||
englishName: "Swedish"
|
||||
},
|
||||
'sw': {
|
||||
nativeName: "Kiswahili",
|
||||
englishName: "Swahili"
|
||||
},
|
||||
'sw-KE': {
|
||||
nativeName: "Kiswahili",
|
||||
englishName: "Swahili (Kenya)"
|
||||
},
|
||||
'ta': {
|
||||
nativeName: "தமிழ்",
|
||||
englishName: "Tamil"
|
||||
},
|
||||
'ta-IN': {
|
||||
nativeName: "தமிழ்",
|
||||
englishName: "Tamil"
|
||||
},
|
||||
'te': {
|
||||
nativeName: "తెలుగు",
|
||||
englishName: "Telugu"
|
||||
},
|
||||
'te-IN': {
|
||||
nativeName: "తెలుగు",
|
||||
englishName: "Telugu"
|
||||
},
|
||||
'tg': {
|
||||
nativeName: "забо́ни тоҷикӣ́",
|
||||
englishName: "Tajik"
|
||||
},
|
||||
'tg-TJ': {
|
||||
nativeName: "тоҷикӣ",
|
||||
englishName: "Tajik"
|
||||
},
|
||||
'th': {
|
||||
nativeName: "ภาษาไทย",
|
||||
englishName: "Thai"
|
||||
},
|
||||
'th-TH': {
|
||||
nativeName: "ภาษาไทย (ประเทศไทย)",
|
||||
englishName: "Thai (Thailand)"
|
||||
},
|
||||
'tl': {
|
||||
nativeName: "Filipino",
|
||||
englishName: "Filipino"
|
||||
},
|
||||
'tl-PH': {
|
||||
nativeName: "Filipino",
|
||||
englishName: "Filipino"
|
||||
},
|
||||
'tlh': {
|
||||
nativeName: "tlhIngan-Hol",
|
||||
englishName: "Klingon"
|
||||
},
|
||||
'tr': {
|
||||
nativeName: "Türkçe",
|
||||
englishName: "Turkish"
|
||||
},
|
||||
'tr-TR': {
|
||||
nativeName: "Türkçe",
|
||||
englishName: "Turkish"
|
||||
},
|
||||
'tt-RU': {
|
||||
nativeName: "татарча",
|
||||
englishName: "Tatar"
|
||||
},
|
||||
'uk': {
|
||||
nativeName: "Українська",
|
||||
englishName: "Ukrainian"
|
||||
},
|
||||
'uk-UA': {
|
||||
nativeName: "Українська",
|
||||
englishName: "Ukrainian"
|
||||
},
|
||||
'ur': {
|
||||
nativeName: "اردو",
|
||||
englishName: "Urdu"
|
||||
},
|
||||
'ur-PK': {
|
||||
nativeName: "اردو",
|
||||
englishName: "Urdu"
|
||||
},
|
||||
'uz': {
|
||||
nativeName: "O'zbek",
|
||||
englishName: "Uzbek"
|
||||
},
|
||||
'uz-UZ': {
|
||||
nativeName: "O'zbek",
|
||||
englishName: "Uzbek"
|
||||
},
|
||||
'vi': {
|
||||
nativeName: "Tiếng Việt",
|
||||
englishName: "Vietnamese"
|
||||
},
|
||||
'vi-VN': {
|
||||
nativeName: "Tiếng Việt",
|
||||
englishName: "Vietnamese"
|
||||
},
|
||||
'xh-ZA': {
|
||||
nativeName: "isiXhosa",
|
||||
englishName: "Xhosa"
|
||||
},
|
||||
'yi': {
|
||||
nativeName: "ייִדיש",
|
||||
englishName: "Yiddish"
|
||||
},
|
||||
'yi-DE': {
|
||||
nativeName: "ייִדיש (German)",
|
||||
englishName: "Yiddish (German)"
|
||||
},
|
||||
'zh': {
|
||||
nativeName: "中文",
|
||||
englishName: "Chinese"
|
||||
},
|
||||
'zh-Hans': {
|
||||
nativeName: "中文简体",
|
||||
englishName: "Chinese Simplified"
|
||||
},
|
||||
'zh-Hant': {
|
||||
nativeName: "中文繁體",
|
||||
englishName: "Chinese Traditional"
|
||||
},
|
||||
'zh-CN': {
|
||||
nativeName: "中文(中国)",
|
||||
englishName: "Chinese Simplified (China)"
|
||||
},
|
||||
'zh-HK': {
|
||||
nativeName: "中文(香港)",
|
||||
englishName: "Chinese Traditional (Hong Kong)"
|
||||
},
|
||||
'zh-SG': {
|
||||
nativeName: "中文(新加坡)",
|
||||
englishName: "Chinese Simplified (Singapore)"
|
||||
},
|
||||
'zh-TW': {
|
||||
nativeName: "中文(台灣)",
|
||||
englishName: "Chinese Traditional (Taiwan)"
|
||||
},
|
||||
'zu-ZA': {
|
||||
nativeName: "isiZulu",
|
||||
englishName: "Zulu"
|
||||
}
|
||||
};
|
||||
|
||||
// We normalize language codes to all lower case, while this table
|
||||
// uses lower-UPPER format for 4 letter codes. This duplicates all entries
|
||||
// in lower case. It's a bit hacky, but lets us keep in sync with updates
|
||||
// to the table without needing to normalize it to lower-lower every time.
|
||||
for (let key of Object.keys(mozilla.LanguageMapping)) {
|
||||
mozilla.LanguageMapping[key.toLowerCase()] = mozilla.LanguageMapping[key];
|
||||
}
|
||||
Vendored
+2590
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,536 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/* We use classes and not ids since there might be multiple
|
||||
shaka elements on the page.*/
|
||||
/* TODO: insert reset attributes into all classes */
|
||||
|
||||
/* Mixins */
|
||||
.user-select(@value: none) {
|
||||
user-select: @value;
|
||||
-webkit-user-select: @value;
|
||||
-moz-user-select: @value;
|
||||
-ms-user-select: @value;
|
||||
}
|
||||
|
||||
.fullscreen() {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.hidden() {
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
||||
/* Styles */
|
||||
/* This insures video and the container have the same height. */
|
||||
.shaka-video {
|
||||
display: block;
|
||||
}
|
||||
|
||||
|
||||
.shaka-overlay-parent {
|
||||
/* Makes this a positioned ancestor of .overlay */
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.shaka-overlay {
|
||||
/* Allows this to be positioned relative to a containing .overlay-parent */
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.shaka-buffering-spinner {
|
||||
margin: auto;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 17px;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.shaka-controls-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
/* The two-value settings are shorthand for setting-top=setting-bottom &
|
||||
setting-left=setting-right. */
|
||||
padding: 12px 5px;
|
||||
margin: auto;
|
||||
box-sizing: border-box;
|
||||
|
||||
/* marks this as a flex container */
|
||||
display: flex;
|
||||
/* Flex-direction property defines in which
|
||||
direction the container wants to stack it's children */
|
||||
flex-direction: column;
|
||||
/* Justify-content is used to align the flex items.
|
||||
Flex-end value aligns the flex items at the end of the
|
||||
container. */
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
transition: opacity 0.3s;
|
||||
|
||||
bottom: 0px;
|
||||
background-image:
|
||||
linear-gradient(to top,
|
||||
rgba(0, 0, 0, 1) 0,
|
||||
rgba(0, 0, 0, 0) 92px);
|
||||
}
|
||||
|
||||
.shaka-controls-button-panel {
|
||||
width: 100%;
|
||||
margin-right: 16px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-end;
|
||||
z-index: 0;
|
||||
overflow: hidden;
|
||||
bottom: auto;
|
||||
|
||||
min-width: 48px;
|
||||
|
||||
font-size: 12px;
|
||||
font-weight: normal; /* Make sure that we don't inherit non-defaults. */
|
||||
font-style: normal;
|
||||
|
||||
.user-select();
|
||||
|
||||
button {
|
||||
color: white;
|
||||
height: 32px;
|
||||
padding: 1px 6px;
|
||||
background: transparent;
|
||||
border: 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
button:active {
|
||||
background: rgba(100, 100, 100, 0.4);
|
||||
}
|
||||
|
||||
button:disabled {
|
||||
color: rgba(255, 255, 255, 0.3);
|
||||
}
|
||||
|
||||
input[type="range"] {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
div {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* Always show controls while casting */
|
||||
&.shaka-casting {
|
||||
opacity: 1;
|
||||
|
||||
.fullscreenButton {
|
||||
/* Hide fullscreen button while casting */
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* NOTE: These fullscreen pseudo-classes can't be combined. Browsers ignore
|
||||
* the rest of the list once they hit one prefix they don't support.
|
||||
*/
|
||||
.shaka-video-container:fullscreen { .fullscreen; }
|
||||
.shaka-video-container:-webkit-full-screen { .fullscreen; }
|
||||
.shaka-video-container:-moz-full-screen { .fullscreen; }
|
||||
.shaka-video-container:-ms-fullscreen { .fullscreen; }
|
||||
|
||||
.shaka-cast-receiver-name {
|
||||
display: none;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
color: white;
|
||||
font-size: 150%;
|
||||
padding: 5px;
|
||||
|
||||
bottom: 50px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
margin: auto;
|
||||
width: max-content;
|
||||
}
|
||||
|
||||
.shaka-play-button-container {
|
||||
/* These settings keep the container in the middle
|
||||
of the video, above the controls */
|
||||
margin: 0 auto;
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 17px;
|
||||
right: 0;
|
||||
/* IE-specific hack for vertical centering */
|
||||
height: 100%;
|
||||
/* These settings keep the button in the middle
|
||||
of the container */
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.shaka-play-button {
|
||||
/* TODO(b/116328412): resize play button in CSS only, not in JS */
|
||||
/* Button width and height will be changed when
|
||||
the dimensions of the video change.
|
||||
See shaka.ui.Controls.prototype.resizePlayButton_ */
|
||||
width: 56px;
|
||||
height: 56px;
|
||||
/* To be properly positioned in the center, this should have no margin or
|
||||
padding. These might have been set for buttons generally by the app or
|
||||
user-agent. */
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
position: relative;
|
||||
top: 14px;
|
||||
|
||||
border-radius: 50%;
|
||||
box-shadow: rgba(0, 0, 0, 0.1) 0px 0px 20px 0px;
|
||||
border: none;
|
||||
/* The play arrow is a picture. It is treated a background image.
|
||||
The following settings insure it shows only once and in the
|
||||
center of the button. */
|
||||
background-size: 50%;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center center;
|
||||
background-color: rgba(255, 255, 255, .9);
|
||||
|
||||
/* Gradually change opacity using cubic-bezier timing function */
|
||||
transition: opacity cubic-bezier(0.4, 0.0, 0.6, 1) 600ms;
|
||||
|
||||
&[icon="play"] {
|
||||
background-image: data-uri('play_arrow.svg');
|
||||
}
|
||||
|
||||
&[icon="pause"] {
|
||||
background-image: data-uri('pause.svg');
|
||||
}
|
||||
}
|
||||
|
||||
.shaka-time-container {
|
||||
font-family: Roboto-Regular, Roboto, sans-serif;
|
||||
font-size: 16px;
|
||||
color: rgb(255, 255, 255);
|
||||
letter-spacing: 0px;
|
||||
|
||||
/* Positioning settings */
|
||||
padding-left: 19px;
|
||||
margin-bottom: 5px;
|
||||
align-self: flex-end;
|
||||
|
||||
cursor: default;
|
||||
.user-select();
|
||||
}
|
||||
|
||||
|
||||
/* NOTE: pseudo-elements for different browsers can't be combined with commas.
|
||||
* Browsers will ignore styles if any pseudo-element in the list is unknown.
|
||||
*/
|
||||
|
||||
/* Seek bar and volume bar */
|
||||
.thumb-style() {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
background-color: rgb(255, 255, 255);
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.track-style() {
|
||||
background-color: transparent;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.shaka-seek-bar {
|
||||
margin: 9px 0 0 0;
|
||||
width: 96.5%;
|
||||
height: 4px;
|
||||
border-radius: 2px;
|
||||
/* Make sure clicking at the very top of the seek bar still takes effect and
|
||||
is not confused with clicking the video to play/pause it */
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
|
||||
.shaka-volume-bar {
|
||||
min-width: 15px;
|
||||
max-width: 100px;
|
||||
position: relative;
|
||||
/* TODO: find a better way to center this thing and remove top */
|
||||
top: 13px;
|
||||
height: 3px;
|
||||
/* spacing consistent with padding in the buttons it lives with */
|
||||
margin: 1px 6px;
|
||||
}
|
||||
|
||||
|
||||
/* hide volume slider on mobile-sized screens */
|
||||
@media screen and (max-width: 550px) {
|
||||
.shaka-volume-bar {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.shaka-seek-bar, .shaka-volume-bar {
|
||||
-webkit-appearance: none;
|
||||
|
||||
/* Explicitly set padding to 0 for IE */
|
||||
padding: 0;
|
||||
|
||||
&::-webkit-slider-thumb {
|
||||
-webkit-appearance: none;
|
||||
.thumb-style();
|
||||
}
|
||||
|
||||
&::-moz-range-thumb {
|
||||
.thumb-style();
|
||||
}
|
||||
|
||||
&::-ms-thumb {
|
||||
.thumb-style();
|
||||
}
|
||||
|
||||
/* turn off tooltips for the seekBar and volumeBar on IE */
|
||||
&::-ms-tooltip {
|
||||
.hidden();
|
||||
}
|
||||
|
||||
/* removes mozilla default styling */
|
||||
&::-moz-range-track {
|
||||
.track-style()
|
||||
}
|
||||
|
||||
/* removes IE default styling */
|
||||
&::-ms-track, &::-ms-fill-lower, &::-ms-fill-upper {
|
||||
.track-style()
|
||||
}
|
||||
}
|
||||
|
||||
.shaka-settings-menu {
|
||||
max-height: 250px;
|
||||
min-width: 180px;
|
||||
|
||||
/* It's okay to add a vertical scroll if there are too many items,
|
||||
but not a horizontal one. */
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
|
||||
white-space: nowrap; /* Don't wrap text to the next line. */
|
||||
background: #FFFFFF;
|
||||
box-shadow: 0 1px 9px 0 rgba(0,0,0,0.40);
|
||||
border-radius: 2px;
|
||||
display: none;
|
||||
flex-direction: column;
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
right: 15px;
|
||||
bottom: 30px;
|
||||
|
||||
button {
|
||||
font-size: 14px;
|
||||
background: transparent;
|
||||
color: black;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border: none;
|
||||
min-height: 30px;
|
||||
padding: 3.5px 6px;
|
||||
|
||||
&:hover {
|
||||
background: rgb(224, 224, 224);
|
||||
}
|
||||
}
|
||||
|
||||
i {
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
&.shaka-low-position {
|
||||
bottom: 15px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.shaka-overflow-menu span {
|
||||
text-align: left;
|
||||
position: relative;
|
||||
font-family: Roboto-Regular, Roboto, sans-serif;
|
||||
left: 13px;
|
||||
}
|
||||
|
||||
.shaka-overflow-button-label {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.shaka-current-selection-span {
|
||||
color: rgba(0, 0, 0, 0.54);
|
||||
}
|
||||
|
||||
|
||||
.shaka-resolutions,
|
||||
.shaka-audio-languages,
|
||||
.shaka-text-languages {
|
||||
span {
|
||||
margin-left: 54px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.shaka-back-to-overflow-button {
|
||||
span {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
i {
|
||||
padding-right: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.shaka-cast-connected-button {
|
||||
.hidden();
|
||||
}
|
||||
|
||||
|
||||
/* Add 'button' to hidden and displayed classes
|
||||
to make them override display setting previously set in
|
||||
.overflowMenu button.
|
||||
See https://goo.gl/egXAJY for explanation of
|
||||
how CSS cascade rules work. */
|
||||
button.shaka-hidden,
|
||||
.shaka-hidden {
|
||||
.hidden();
|
||||
}
|
||||
|
||||
button.shaka-displayed,
|
||||
.shaka-displayed {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.shaka-opaque {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.shaka-transparent {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
The SVG/CSS buffering spinner is based on http://codepen.io/jczimm/pen/vEBpoL
|
||||
Some local modifications have been made.
|
||||
|
||||
Copyright (c) 2016 by jczimm
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do
|
||||
so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
|
||||
.shaka-spinner-svg {
|
||||
animation: rotate 2s linear infinite;
|
||||
height: 100%;
|
||||
transform-origin: center center;
|
||||
width: 100%;
|
||||
position: absolute;
|
||||
top: 0; bottom: 0; left: 0; right: 0;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.shaka-spinner-path {
|
||||
/* Fall back for IE 11, where the stroke properties are not animated,
|
||||
but the spinner still rotates. */
|
||||
stroke: #202124;
|
||||
stroke-dasharray: 20, 200;
|
||||
stroke-dashoffset: 0;
|
||||
|
||||
animation:
|
||||
dash 1.5s ease-in-out infinite,
|
||||
color 6s ease-in-out infinite;
|
||||
stroke-linecap: round;
|
||||
}
|
||||
|
||||
@keyframes rotate {
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes dash {
|
||||
0% {
|
||||
stroke-dasharray: 1, 200;
|
||||
stroke-dashoffset: 0;
|
||||
}
|
||||
50% {
|
||||
stroke-dasharray: 89, 200;
|
||||
stroke-dashoffset: -35px;
|
||||
}
|
||||
100% {
|
||||
stroke-dasharray: 89, 200;
|
||||
stroke-dashoffset: -124px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* Outline on focus is important for accessibility, but
|
||||
it doesn't look great. This removes the outline for
|
||||
mouse users while leaving it for keyboard users. */
|
||||
.shaka-controls-container:not(.shaka-keyboard-navigation) {
|
||||
button:focus,
|
||||
input:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
button::-moz-focus-inner,
|
||||
input::-moz-focus-outer {
|
||||
border: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.shaka-auto-span {
|
||||
left: 17px;
|
||||
}
|
||||
|
||||
.shaka-captions-on {
|
||||
color: black;
|
||||
}
|
||||
|
||||
.shaka-captions-off {
|
||||
color: grey;
|
||||
}
|
||||
+578
@@ -0,0 +1,578 @@
|
||||
/**
|
||||
* @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.
|
||||
*/
|
||||
|
||||
|
||||
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
// 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".
|
||||
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
|
||||
goog.provide('shaka.ui.Locales');
|
||||
goog.provide('shaka.ui.Locales.Ids');
|
||||
goog.require('shaka.ui.Localization');
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
|
||||
shaka.ui.Locales.apply = function(localization) {
|
||||
localization.insert('ar', new Map([
|
||||
['1050953507607739202', 'الحجم'],
|
||||
['1077325112364709655', 'رجوع'],
|
||||
['1142734805932039923', 'إرجاع'],
|
||||
['1774834209035716827', 'تقديم سريع'],
|
||||
['1911090580951495029', 'الترجمة'],
|
||||
['2023925063728908356', 'إلغاء كتم الصوت'],
|
||||
['298626259350585300', 'غير معروفة'],
|
||||
['3045980486001972586', 'مباشر'],
|
||||
['3278592358864783064', 'اللغة'],
|
||||
['4259064532355692191', 'تلقائي'],
|
||||
['4388316720828367903', 'إعدادات إضافية'],
|
||||
['5553522235935533682', 'شريط تمرير البحث'],
|
||||
['5963689277976480680', 'كتم الصوت'],
|
||||
['6161306839322897077', 'إنهاء وضع ملء الشاشة'],
|
||||
['7071612439610534706', 'إرسال...'],
|
||||
['8145129506114534451', 'إيقاف'],
|
||||
['8345190086337560158', 'ملء الشاشة'],
|
||||
['836055097473758014', 'تشغيل'],
|
||||
['9042260521669277115', 'إيقاف مؤقت'],
|
||||
]));
|
||||
localization.insert('de', new Map([
|
||||
['1050953507607739202', 'Lautstärke'],
|
||||
['1077325112364709655', 'Zurück'],
|
||||
['1142734805932039923', 'Zurückspulen'],
|
||||
['1774834209035716827', 'Vorspulen'],
|
||||
['1911090580951495029', 'Untertitel'],
|
||||
['2023925063728908356', 'Stummschaltung aufheben'],
|
||||
['298626259350585300', 'Unbekannt'],
|
||||
['3045980486001972586', 'Live'],
|
||||
['3278592358864783064', 'Sprache'],
|
||||
['411375375680850814', 'Nicht zutreffend'],
|
||||
['4259064532355692191', 'Automatisch'],
|
||||
['4388316720828367903', 'Weitere Einstellungen'],
|
||||
['5553522235935533682', 'Schieberegler für Suche'],
|
||||
['5963689277976480680', 'Stummschalten'],
|
||||
['6073266792045231479', 'Auflösung'],
|
||||
['6161306839322897077', 'Vollbildmodus beenden'],
|
||||
['7071612439610534706', 'Streamen…'],
|
||||
['8145129506114534451', 'Aus'],
|
||||
['8345190086337560158', 'Vollbild'],
|
||||
['836055097473758014', 'Wiedergeben'],
|
||||
['9042260521669277115', 'Pausieren'],
|
||||
]));
|
||||
localization.insert('en', new Map([
|
||||
['1050953507607739202', 'volume'],
|
||||
['1077325112364709655', 'Back'],
|
||||
['1142734805932039923', 'Rewind'],
|
||||
['1774834209035716827', 'Fast-forward'],
|
||||
['1911090580951495029', 'Captions'],
|
||||
['2023925063728908356', 'Unmute'],
|
||||
['298626259350585300', 'Unknown'],
|
||||
['3045980486001972586', 'Live'],
|
||||
['3278592358864783064', 'Language'],
|
||||
['411375375680850814', 'N/A'],
|
||||
['4259064532355692191', 'Auto'],
|
||||
['4388316720828367903', 'More settings'],
|
||||
['5553522235935533682', 'Seek slider'],
|
||||
['5963689277976480680', 'Mute'],
|
||||
['6073266792045231479', 'Resolution'],
|
||||
['6161306839322897077', 'Exit full screen'],
|
||||
['7071612439610534706', 'Cast...'],
|
||||
['8145129506114534451', 'Off'],
|
||||
['8345190086337560158', 'Full screen'],
|
||||
['836055097473758014', 'Play'],
|
||||
['9042260521669277115', 'Pause'],
|
||||
]));
|
||||
localization.insert('en-GB', new Map([
|
||||
['1050953507607739202', 'volume'],
|
||||
['1077325112364709655', 'Back'],
|
||||
['1142734805932039923', 'Rewind'],
|
||||
['1774834209035716827', 'Fast-forward'],
|
||||
['1911090580951495029', 'Captions'],
|
||||
['2023925063728908356', 'Unmute'],
|
||||
['298626259350585300', 'Unknown'],
|
||||
['3045980486001972586', 'Live'],
|
||||
['3278592358864783064', 'Language'],
|
||||
['4259064532355692191', 'Auto'],
|
||||
['4388316720828367903', 'More settings'],
|
||||
['5553522235935533682', 'Seek slider'],
|
||||
['5963689277976480680', 'Mute'],
|
||||
['6073266792045231479', 'Resolution'],
|
||||
['6161306839322897077', 'Exit full screen'],
|
||||
['7071612439610534706', 'Cast...'],
|
||||
['8145129506114534451', 'Off'],
|
||||
['8345190086337560158', 'Full screen'],
|
||||
['836055097473758014', 'Play'],
|
||||
['9042260521669277115', 'Pause'],
|
||||
]));
|
||||
localization.insert('es', new Map([
|
||||
['1050953507607739202', 'volumen'],
|
||||
['1077325112364709655', 'Atrás'],
|
||||
['1142734805932039923', 'Retroceder'],
|
||||
['1774834209035716827', 'Avance rápido'],
|
||||
['1911090580951495029', 'Subtítulos'],
|
||||
['2023925063728908356', 'Activar sonido'],
|
||||
['298626259350585300', 'Desconocido'],
|
||||
['3045980486001972586', 'En directo'],
|
||||
['3278592358864783064', 'Idioma'],
|
||||
['411375375680850814', 'No aplicable'],
|
||||
['4259064532355692191', 'Automática'],
|
||||
['4388316720828367903', 'Más ajustes'],
|
||||
['5553522235935533682', 'Barra deslizante de búsqueda'],
|
||||
['5963689277976480680', 'Silenciar'],
|
||||
['6073266792045231479', 'Resolución'],
|
||||
['6161306839322897077', 'Salir del modo de pantalla completa'],
|
||||
['7071612439610534706', 'Reparto...'],
|
||||
['8145129506114534451', 'No'],
|
||||
['8345190086337560158', 'Pantalla completa'],
|
||||
['836055097473758014', 'Reproducir'],
|
||||
['9042260521669277115', 'Pausa'],
|
||||
]));
|
||||
localization.insert('es-419', new Map([
|
||||
['1050953507607739202', 'volumen'],
|
||||
['1077325112364709655', 'Atrás'],
|
||||
['1142734805932039923', 'Retroceder'],
|
||||
['1774834209035716827', 'Avance rápido'],
|
||||
['1911090580951495029', 'Subtítulos'],
|
||||
['2023925063728908356', 'Activar sonido'],
|
||||
['298626259350585300', 'Desconocido'],
|
||||
['3045980486001972586', 'En vivo'],
|
||||
['3278592358864783064', 'Idioma'],
|
||||
['4259064532355692191', 'Auto'],
|
||||
['4388316720828367903', 'Más opciones de configuración'],
|
||||
['5553522235935533682', 'Barra deslizante de búsqueda'],
|
||||
['5963689277976480680', 'Silenciar'],
|
||||
['6073266792045231479', 'Resolución'],
|
||||
['6161306839322897077', 'Salir de pantalla completa'],
|
||||
['7071612439610534706', 'Transmitir…'],
|
||||
['8145129506114534451', 'Desactivado'],
|
||||
['8345190086337560158', 'Pantalla completa'],
|
||||
['836055097473758014', 'Jugar'],
|
||||
['9042260521669277115', 'Detener'],
|
||||
]));
|
||||
localization.insert('fr', new Map([
|
||||
['1050953507607739202', 'volume'],
|
||||
['1077325112364709655', 'Retour'],
|
||||
['1142734805932039923', 'Retour arrière'],
|
||||
['1774834209035716827', 'Avance rapide'],
|
||||
['1911090580951495029', 'Sous-titres'],
|
||||
['2023925063728908356', 'Activer le son'],
|
||||
['298626259350585300', 'Inconnue'],
|
||||
['3045980486001972586', 'En direct'],
|
||||
['3278592358864783064', 'Langue'],
|
||||
['411375375680850814', 'Sans objet'],
|
||||
['4259064532355692191', 'Auto'],
|
||||
['4388316720828367903', 'Autres paramètres'],
|
||||
['5553522235935533682', 'Barre de recherche'],
|
||||
['5963689277976480680', 'Désactiver le son'],
|
||||
['6073266792045231479', 'Résolution'],
|
||||
['6161306839322897077', 'Quitter le mode plein écran'],
|
||||
['7071612439610534706', 'Caster sur…'],
|
||||
['8145129506114534451', 'Désactivée'],
|
||||
['8345190086337560158', 'Plein écran'],
|
||||
['836055097473758014', 'Lire'],
|
||||
['9042260521669277115', 'Mettre en veille'],
|
||||
]));
|
||||
localization.insert('it', new Map([
|
||||
['1050953507607739202', 'volume'],
|
||||
['1077325112364709655', 'Indietro'],
|
||||
['1142734805932039923', 'Riavvolgi'],
|
||||
['1774834209035716827', 'Avanti veloce'],
|
||||
['1911090580951495029', 'Sottotitoli'],
|
||||
['2023925063728908356', 'Riattiva audio'],
|
||||
['298626259350585300', 'Sconosciuto'],
|
||||
['3045980486001972586', 'Dal vivo'],
|
||||
['3278592358864783064', 'Lingua'],
|
||||
['411375375680850814', 'N/A'],
|
||||
['4259064532355692191', 'Auto'],
|
||||
['4388316720828367903', 'Altre impostazioni'],
|
||||
['5553522235935533682', 'Dispositivo di scorrimento'],
|
||||
['5963689277976480680', 'Disattiva audio'],
|
||||
['6073266792045231479', 'Risoluzione'],
|
||||
['6161306839322897077', 'Esci dalla modalità a schermo intero'],
|
||||
['7071612439610534706', 'Trasmetti…'],
|
||||
['8145129506114534451', 'Disattivato'],
|
||||
['8345190086337560158', 'Schermo intero'],
|
||||
['836055097473758014', 'Riproduci'],
|
||||
['9042260521669277115', 'Metti in pausa'],
|
||||
]));
|
||||
localization.insert('ja', new Map([
|
||||
['1050953507607739202', '音量'],
|
||||
['1077325112364709655', '戻る'],
|
||||
['1142734805932039923', '巻き戻し'],
|
||||
['1774834209035716827', '早送り'],
|
||||
['1911090580951495029', '字幕'],
|
||||
['2023925063728908356', 'ミュート解除'],
|
||||
['298626259350585300', '不明'],
|
||||
['3045980486001972586', 'ライブ'],
|
||||
['3278592358864783064', '言語'],
|
||||
['411375375680850814', '該当なし'],
|
||||
['4259064532355692191', '自動'],
|
||||
['4388316720828367903', 'その他の設定'],
|
||||
['5553522235935533682', 'シーク バー'],
|
||||
['5963689277976480680', 'ミュート'],
|
||||
['6073266792045231479', '解像度'],
|
||||
['6161306839322897077', '全画面モードの終了'],
|
||||
['7071612439610534706', 'キャスト...'],
|
||||
['8145129506114534451', 'オフ'],
|
||||
['8345190086337560158', '全画面'],
|
||||
['836055097473758014', '再生'],
|
||||
['9042260521669277115', '一時停止'],
|
||||
]));
|
||||
localization.insert('ko', new Map([
|
||||
['1050953507607739202', '볼륨'],
|
||||
['1077325112364709655', '뒤로'],
|
||||
['1142734805932039923', '되감기'],
|
||||
['1774834209035716827', '빨리감기'],
|
||||
['1911090580951495029', '자막'],
|
||||
['2023925063728908356', '음소거 해제'],
|
||||
['298626259350585300', '알 수 없음'],
|
||||
['3045980486001972586', '라이브'],
|
||||
['3278592358864783064', '언어'],
|
||||
['411375375680850814', '해당 없음'],
|
||||
['4259064532355692191', '자동'],
|
||||
['4388316720828367903', '설정 더보기'],
|
||||
['5553522235935533682', '탐색 슬라이더'],
|
||||
['5963689277976480680', '음소거'],
|
||||
['6073266792045231479', '해상도'],
|
||||
['6161306839322897077', '전체화면 종료'],
|
||||
['7071612439610534706', '전송...'],
|
||||
['8145129506114534451', '사용 안함'],
|
||||
['8345190086337560158', '전체화면'],
|
||||
['836055097473758014', '재생'],
|
||||
['9042260521669277115', '일시중지'],
|
||||
]));
|
||||
localization.insert('nl', new Map([
|
||||
['1050953507607739202', 'volume'],
|
||||
['1077325112364709655', 'Terug'],
|
||||
['1142734805932039923', 'Terugspoelen'],
|
||||
['1774834209035716827', 'Vooruitspoelen'],
|
||||
['1911090580951495029', 'Ondertiteling'],
|
||||
['2023925063728908356', 'Dempen opheffen'],
|
||||
['298626259350585300', 'Onbekend'],
|
||||
['3045980486001972586', 'Live'],
|
||||
['3278592358864783064', 'Taal'],
|
||||
['411375375680850814', 'N.v.t.'],
|
||||
['4259064532355692191', 'Automatisch'],
|
||||
['4388316720828367903', 'Meer instellingen'],
|
||||
['5553522235935533682', 'Zoekschuifbalk'],
|
||||
['5963689277976480680', 'Dempen'],
|
||||
['6073266792045231479', 'Resolutie'],
|
||||
['6161306839322897077', 'Volledig scherm afsluiten'],
|
||||
['7071612439610534706', 'Casten...'],
|
||||
['8145129506114534451', 'Uit'],
|
||||
['8345190086337560158', 'Volledig scherm'],
|
||||
['836055097473758014', 'Afspelen'],
|
||||
['9042260521669277115', 'Onderbreken'],
|
||||
]));
|
||||
localization.insert('pl', new Map([
|
||||
['1050953507607739202', 'głośność'],
|
||||
['1077325112364709655', 'Wstecz'],
|
||||
['1142734805932039923', 'Przewiń do tyłu'],
|
||||
['1774834209035716827', 'Przewiń do przodu'],
|
||||
['1911090580951495029', 'Napisy'],
|
||||
['2023925063728908356', 'Wyłącz wyciszenie'],
|
||||
['298626259350585300', 'Nieznane'],
|
||||
['3045980486001972586', 'Na żywo'],
|
||||
['3278592358864783064', 'Język'],
|
||||
['411375375680850814', 'N/d'],
|
||||
['4259064532355692191', 'Automatyczna'],
|
||||
['4388316720828367903', 'Więcej ustawień'],
|
||||
['5553522235935533682', 'Suwak przewijania'],
|
||||
['5963689277976480680', 'Wycisz'],
|
||||
['6073266792045231479', 'Rozdzielczość'],
|
||||
['6161306839322897077', 'Zamknij pełny ekran'],
|
||||
['7071612439610534706', 'Prześlij...'],
|
||||
['8145129506114534451', 'Wyłączone'],
|
||||
['8345190086337560158', 'Pełny ekran'],
|
||||
['836055097473758014', 'Odtwarzaj'],
|
||||
['9042260521669277115', 'Wstrzymaj'],
|
||||
]));
|
||||
localization.insert('pt-BR', new Map([
|
||||
['1050953507607739202', 'volume'],
|
||||
['1077325112364709655', 'Voltar'],
|
||||
['1142734805932039923', 'Retroceder'],
|
||||
['1774834209035716827', 'Avançar'],
|
||||
['1911090580951495029', 'Legendas ocultas'],
|
||||
['2023925063728908356', 'Ativar som'],
|
||||
['298626259350585300', 'Desconhecido'],
|
||||
['3045980486001972586', 'Ao vivo'],
|
||||
['3278592358864783064', 'Idioma'],
|
||||
['411375375680850814', 'N/A'],
|
||||
['4259064532355692191', 'Automático'],
|
||||
['4388316720828367903', 'Mais configurações'],
|
||||
['5553522235935533682', 'Botão deslizante de busca'],
|
||||
['5963689277976480680', 'Desativar som'],
|
||||
['6073266792045231479', 'Resolução'],
|
||||
['6161306839322897077', 'Sair da tela inteira'],
|
||||
['7071612439610534706', 'Elenco...'],
|
||||
['8145129506114534451', 'Desativado'],
|
||||
['8345190086337560158', 'Tela inteira'],
|
||||
['836055097473758014', 'Reproduzir'],
|
||||
['9042260521669277115', 'Pausar'],
|
||||
]));
|
||||
localization.insert('pt-PT', new Map([
|
||||
['1050953507607739202', 'volume'],
|
||||
['1077325112364709655', 'Anterior'],
|
||||
['1142734805932039923', 'Recuar'],
|
||||
['1774834209035716827', 'Avançar'],
|
||||
['1911090580951495029', 'Legendas'],
|
||||
['2023925063728908356', 'Reativar o som'],
|
||||
['298626259350585300', 'Desconhecida'],
|
||||
['3045980486001972586', 'Em direto'],
|
||||
['3278592358864783064', 'Idioma'],
|
||||
['411375375680850814', 'N/A'],
|
||||
['4259064532355692191', 'Automático'],
|
||||
['4388316720828367903', 'Mais definições'],
|
||||
['5553522235935533682', 'Controlo de deslize da procura'],
|
||||
['5963689277976480680', 'Desativar o som'],
|
||||
['6073266792045231479', 'Resolução'],
|
||||
['6161306839322897077', 'Sair do ecrã inteiro'],
|
||||
['7071612439610534706', 'Transmitir...'],
|
||||
['8145129506114534451', 'Desativado'],
|
||||
['8345190086337560158', 'Ecrã inteiro'],
|
||||
['836055097473758014', 'Reproduzir'],
|
||||
['9042260521669277115', 'Colocar em pausa'],
|
||||
]));
|
||||
localization.insert('ru', new Map([
|
||||
['1050953507607739202', 'громкость'],
|
||||
['1077325112364709655', 'Назад'],
|
||||
['1142734805932039923', 'Перемотать назад'],
|
||||
['1774834209035716827', 'Перемотать вперед'],
|
||||
['1911090580951495029', 'Субтитры'],
|
||||
['2023925063728908356', 'Включить звук'],
|
||||
['298626259350585300', 'Неизвестно'],
|
||||
['3045980486001972586', 'В эфире'],
|
||||
['3278592358864783064', 'Язык'],
|
||||
['411375375680850814', '–'],
|
||||
['4259064532355692191', 'Автонастройка'],
|
||||
['4388316720828367903', 'Дополнительные настройки'],
|
||||
['5553522235935533682', 'Ползунок поиска'],
|
||||
['5963689277976480680', 'Отключить звук'],
|
||||
['6073266792045231479', 'Разрешение'],
|
||||
['6161306839322897077', 'Выход из полноэкранного режима'],
|
||||
['7071612439610534706', 'Добавить'],
|
||||
['8145129506114534451', 'Выкл.'],
|
||||
['8345190086337560158', 'Во весь экран'],
|
||||
['836055097473758014', 'Смотреть'],
|
||||
['9042260521669277115', 'Приостановить'],
|
||||
]));
|
||||
localization.insert('th', new Map([
|
||||
['1050953507607739202', 'ระดับเสียง'],
|
||||
['1077325112364709655', 'กลับ'],
|
||||
['1142734805932039923', 'กรอกลับ'],
|
||||
['1774834209035716827', 'กรอไปข้างหน้า'],
|
||||
['1911090580951495029', 'คำอธิบายวิดีโอ'],
|
||||
['2023925063728908356', 'เปิดเสียง'],
|
||||
['298626259350585300', 'ไม่ทราบ'],
|
||||
['3045980486001972586', 'สด'],
|
||||
['3278592358864783064', 'ภาษา'],
|
||||
['4259064532355692191', 'อัตโนมัติ'],
|
||||
['4388316720828367903', 'การตั้งค่าเพิ่มเติม'],
|
||||
['5553522235935533682', 'แถบเลื่อนค้นหา'],
|
||||
['5963689277976480680', 'ปิดเสียง'],
|
||||
['6073266792045231479', 'ความละเอียด'],
|
||||
['6161306839322897077', 'ออกจากโหมดเต็มหน้าจอ'],
|
||||
['7071612439610534706', 'แคสต์...'],
|
||||
['8145129506114534451', 'ปิด'],
|
||||
['8345190086337560158', 'เต็มหน้าจอ'],
|
||||
['836055097473758014', 'เล่น'],
|
||||
['9042260521669277115', 'หยุดชั่วคราว'],
|
||||
]));
|
||||
localization.insert('tr', new Map([
|
||||
['1050953507607739202', 'ses düzeyi'],
|
||||
['1077325112364709655', 'Geri'],
|
||||
['1142734805932039923', 'Geri sar'],
|
||||
['1774834209035716827', 'İleri sar'],
|
||||
['1911090580951495029', 'Altyazılar'],
|
||||
['2023925063728908356', 'Sesi aç'],
|
||||
['298626259350585300', 'Bilinmiyor'],
|
||||
['3045980486001972586', 'Canlı'],
|
||||
['3278592358864783064', 'Dil'],
|
||||
['4259064532355692191', 'Otomatik'],
|
||||
['4388316720828367903', 'Diğer ayarlar'],
|
||||
['5553522235935533682', 'Arama kaydırma çubuğu'],
|
||||
['5963689277976480680', 'Sesi kapat'],
|
||||
['6073266792045231479', 'Çözünürlük'],
|
||||
['6161306839322897077', 'Tam ekrandan çık'],
|
||||
['7071612439610534706', 'Yayınla...'],
|
||||
['8145129506114534451', 'Kapalı'],
|
||||
['8345190086337560158', 'Tam ekran'],
|
||||
['836055097473758014', 'Oynat'],
|
||||
['9042260521669277115', 'Duraklat'],
|
||||
]));
|
||||
localization.insert('zh-CN', new Map([
|
||||
['1050953507607739202', '音量'],
|
||||
['1077325112364709655', '返回'],
|
||||
['1142734805932039923', '快退'],
|
||||
['1774834209035716827', '快进'],
|
||||
['1911090580951495029', '字幕'],
|
||||
['2023925063728908356', '取消静音'],
|
||||
['298626259350585300', '未知'],
|
||||
['3045980486001972586', '直播'],
|
||||
['3278592358864783064', '语言'],
|
||||
['4259064532355692191', '自动'],
|
||||
['4388316720828367903', '更多设置'],
|
||||
['5553522235935533682', '播放滑块'],
|
||||
['5963689277976480680', '静音'],
|
||||
['6073266792045231479', '分辨率'],
|
||||
['6161306839322897077', '退出全屏'],
|
||||
['7071612439610534706', '投射…'],
|
||||
['8145129506114534451', '关闭'],
|
||||
['8345190086337560158', '全屏'],
|
||||
['836055097473758014', '播放'],
|
||||
['9042260521669277115', '暂停'],
|
||||
]));
|
||||
localization.insert('zh-HK', new Map([
|
||||
['1050953507607739202', '音量'],
|
||||
['1077325112364709655', '返回'],
|
||||
['1142734805932039923', '倒帶'],
|
||||
['1774834209035716827', '快轉'],
|
||||
['1911090580951495029', '字幕'],
|
||||
['2023925063728908356', '解除靜音'],
|
||||
['298626259350585300', '不明'],
|
||||
['3045980486001972586', '直播'],
|
||||
['3278592358864783064', '語言'],
|
||||
['4259064532355692191', '自動'],
|
||||
['4388316720828367903', '更多設定'],
|
||||
['5553522235935533682', '搜尋滑桿'],
|
||||
['5963689277976480680', '靜音'],
|
||||
['6161306839322897077', '結束全螢幕'],
|
||||
['7071612439610534706', '投放…'],
|
||||
['8145129506114534451', '未選取'],
|
||||
['8345190086337560158', '全螢幕'],
|
||||
['836055097473758014', '播放'],
|
||||
['9042260521669277115', '暫停'],
|
||||
]));
|
||||
localization.insert('zh-TW', new Map([
|
||||
['1050953507607739202', '音量'],
|
||||
['1077325112364709655', '返回'],
|
||||
['1142734805932039923', '倒轉'],
|
||||
['1774834209035716827', '快轉'],
|
||||
['1911090580951495029', '字幕'],
|
||||
['2023925063728908356', '解除靜音'],
|
||||
['298626259350585300', '未知'],
|
||||
['3045980486001972586', '直播'],
|
||||
['3278592358864783064', '語言'],
|
||||
['411375375680850814', '不適用'],
|
||||
['4259064532355692191', '自動'],
|
||||
['4388316720828367903', '更多設定'],
|
||||
['5553522235935533682', '搜尋滑桿'],
|
||||
['5963689277976480680', '靜音'],
|
||||
['6073266792045231479', '解析度'],
|
||||
['6161306839322897077', '結束全螢幕'],
|
||||
['7071612439610534706', '投放…'],
|
||||
['8145129506114534451', '關閉'],
|
||||
['8345190086337560158', '全螢幕'],
|
||||
['836055097473758014', '播放'],
|
||||
['9042260521669277115', '暫停'],
|
||||
]));
|
||||
};
|
||||
|
||||
/** @const {string} */
|
||||
shaka.ui.Locales.Ids.ARIA_LABEL_BACK = '1077325112364709655';
|
||||
|
||||
/** @const {string} */
|
||||
shaka.ui.Locales.Ids.ARIA_LABEL_CAPTIONS = '1911090580951495029';
|
||||
|
||||
/** @const {string} */
|
||||
shaka.ui.Locales.Ids.ARIA_LABEL_CAST = '7071612439610534706';
|
||||
|
||||
/** @const {string} */
|
||||
shaka.ui.Locales.Ids.ARIA_LABEL_EXIT_FULL_SCREEN = '6161306839322897077';
|
||||
|
||||
/** @const {string} */
|
||||
shaka.ui.Locales.Ids.ARIA_LABEL_FAST_FORWARD = '1774834209035716827';
|
||||
|
||||
/** @const {string} */
|
||||
shaka.ui.Locales.Ids.ARIA_LABEL_FULL_SCREEN = '8345190086337560158';
|
||||
|
||||
/** @const {string} */
|
||||
shaka.ui.Locales.Ids.ARIA_LABEL_LANGUAGE = '3278592358864783064';
|
||||
|
||||
/** @const {string} */
|
||||
shaka.ui.Locales.Ids.ARIA_LABEL_LIVE = '3045980486001972586';
|
||||
|
||||
/** @const {string} */
|
||||
shaka.ui.Locales.Ids.ARIA_LABEL_MORE_SETTINGS = '4388316720828367903';
|
||||
|
||||
/** @const {string} */
|
||||
shaka.ui.Locales.Ids.ARIA_LABEL_MUTE = '5963689277976480680';
|
||||
|
||||
/** @const {string} */
|
||||
shaka.ui.Locales.Ids.ARIA_LABEL_PAUSE = '9042260521669277115';
|
||||
|
||||
/** @const {string} */
|
||||
shaka.ui.Locales.Ids.ARIA_LABEL_PLAY = '836055097473758014';
|
||||
|
||||
/** @const {string} */
|
||||
shaka.ui.Locales.Ids.ARIA_LABEL_RESOLUTION = '6073266792045231479';
|
||||
|
||||
/** @const {string} */
|
||||
shaka.ui.Locales.Ids.ARIA_LABEL_REWIND = '1142734805932039923';
|
||||
|
||||
/** @const {string} */
|
||||
shaka.ui.Locales.Ids.ARIA_LABEL_SEEK = '5553522235935533682';
|
||||
|
||||
/** @const {string} */
|
||||
shaka.ui.Locales.Ids.ARIA_LABEL_UNMUTE = '2023925063728908356';
|
||||
|
||||
/** @const {string} */
|
||||
shaka.ui.Locales.Ids.ARIA_LABEL_VOLUME = '1050953507607739202';
|
||||
|
||||
/** @const {string} */
|
||||
shaka.ui.Locales.Ids.LABEL_AUTO_QUALITY = '4259064532355692191';
|
||||
|
||||
/** @const {string} */
|
||||
shaka.ui.Locales.Ids.LABEL_CAPTIONS = '1911090580951495029';
|
||||
|
||||
/** @const {string} */
|
||||
shaka.ui.Locales.Ids.LABEL_CAPTIONS_OFF = '8145129506114534451';
|
||||
|
||||
/** @const {string} */
|
||||
shaka.ui.Locales.Ids.LABEL_CAST = '7071612439610534706';
|
||||
|
||||
/** @const {string} */
|
||||
shaka.ui.Locales.Ids.LABEL_LANGUAGE = '3278592358864783064';
|
||||
|
||||
/** @const {string} */
|
||||
shaka.ui.Locales.Ids.LABEL_LIVE = '3045980486001972586';
|
||||
|
||||
/** @const {string} */
|
||||
shaka.ui.Locales.Ids.LABEL_MULTIPLE_LANGUAGES = '411375375680850814';
|
||||
|
||||
/** @const {string} */
|
||||
shaka.ui.Locales.Ids.LABEL_NOT_APPLICABLE = '411375375680850814';
|
||||
|
||||
/** @const {string} */
|
||||
shaka.ui.Locales.Ids.LABEL_NOT_CASTING = '8145129506114534451';
|
||||
|
||||
/** @const {string} */
|
||||
shaka.ui.Locales.Ids.LABEL_RESOLUTION = '6073266792045231479';
|
||||
|
||||
/** @const {string} */
|
||||
shaka.ui.Locales.Ids.LABEL_UNKNOWN_LANGUAGE = '298626259350585300';
|
||||
@@ -0,0 +1,505 @@
|
||||
/**
|
||||
* @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.
|
||||
*/
|
||||
|
||||
|
||||
goog.provide('shaka.ui.Localization');
|
||||
goog.provide('shaka.ui.Localization.ConflictResolution');
|
||||
|
||||
goog.require('shaka.util.FakeEvent');
|
||||
goog.require('shaka.util.FakeEventTarget');
|
||||
goog.require('shaka.util.Iterables');
|
||||
goog.require('shaka.util.LanguageUtils');
|
||||
|
||||
|
||||
// TODO: link to the design and usage documentation here
|
||||
// b/117679670
|
||||
/**
|
||||
* Localization system provided by the shaka ui library.
|
||||
* It can be used to store the various localized forms of
|
||||
* strings that are expected to be displayed to the user.
|
||||
* If a string is not available, it will return the localized
|
||||
* form in the closest related locale.
|
||||
*
|
||||
* @implements {EventTarget}
|
||||
* @final
|
||||
* @export
|
||||
*/
|
||||
shaka.ui.Localization = class {
|
||||
/**
|
||||
* @param {string} fallbackLocale
|
||||
* The fallback locale that should be used. It will be assumed that this
|
||||
* locale should have entries for just about every request.
|
||||
*/
|
||||
constructor(fallbackLocale) {
|
||||
/** @private {string} */
|
||||
this.fallbackLocale_ = shaka.util.LanguageUtils.normalize(fallbackLocale);
|
||||
|
||||
/**
|
||||
* The current mappings that will be used when requests are made. Since
|
||||
* nothing has been loaded yet, there will be nothing in this map.
|
||||
*
|
||||
* @private {!Map.<string, string>}
|
||||
*/
|
||||
this.currentMap_ = new Map();
|
||||
|
||||
/**
|
||||
* The locales that were used when creating |currentMap_|. Since we don't
|
||||
* have anything when we first initialize, an empty set means "no
|
||||
* preference".
|
||||
*
|
||||
* @private {!Set.<string>}
|
||||
*/
|
||||
this.currentLocales_ = new Set();
|
||||
|
||||
/**
|
||||
* A map of maps where:
|
||||
* - The outer map is a mapping from locale code to localizations.
|
||||
* - The inner map is a mapping from id to localized text.
|
||||
*
|
||||
* @private {!Map.<string, !Map.<string, string>>}
|
||||
*/
|
||||
this.localizations_ = new Map();
|
||||
|
||||
/**
|
||||
* The event target that we will wrap so that we can fire events
|
||||
* without having to manage the listeners directly.
|
||||
*
|
||||
* @private {!EventTarget}
|
||||
*/
|
||||
this.events_ = new shaka.util.FakeEventTarget();
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
* @export
|
||||
*/
|
||||
addEventListener(type, listener, options) {
|
||||
this.events_.addEventListener(type, listener, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
* @export
|
||||
*/
|
||||
removeEventListener(type, listener, options) {
|
||||
// Apparently Closure says we can be passed a null |option|, but we can't
|
||||
// pass a null option, so if we get have a null-like |option|, force it to
|
||||
// be undefined.
|
||||
this.events_.removeEventListener(type, listener, options || undefined);
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
* @export
|
||||
*/
|
||||
dispatchEvent(event) {
|
||||
return this.events_.dispatchEvent(event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Request the localization system to change which locale it serves. If any of
|
||||
* of the preferred locales cannot be found, the localization system will fire
|
||||
* an event identifying which locales it does not know. The localization
|
||||
* system will then continue to operate using the closest matches it has.
|
||||
*
|
||||
* @param {!Iterable.<string>} locales
|
||||
* The locale codes for the requested locales in order of preference.
|
||||
* @export
|
||||
*/
|
||||
changeLocale(locales) {
|
||||
const Class = shaka.ui.Localization;
|
||||
|
||||
// Normalize the locale so that matching will be easier. We need to reset
|
||||
// our internal set of locales so that we have the same order as the new
|
||||
// set.
|
||||
this.currentLocales_.clear();
|
||||
for (const locale of locales) {
|
||||
this.currentLocales_.add(shaka.util.LanguageUtils.normalize(locale));
|
||||
}
|
||||
|
||||
this.updateCurrentMap_();
|
||||
|
||||
this.events_.dispatchEvent(new shaka.util.FakeEvent(Class.LOCALE_CHANGED));
|
||||
|
||||
// Check if we have support for the exact locale requested. Even through we
|
||||
// will do our best to return the most relevant results, we need to tell
|
||||
// app that some data may be missing.
|
||||
const missing = shaka.util.Iterables.filter(
|
||||
this.currentLocales_,
|
||||
(locale) => !this.localizations_.has(locale));
|
||||
|
||||
if (missing.length) {
|
||||
/** @type {shaka.ui.Localization.UnknownLocalesEvent} */
|
||||
const e = {
|
||||
locales: missing,
|
||||
};
|
||||
|
||||
this.events_.dispatchEvent(new shaka.util.FakeEvent(
|
||||
Class.UNKNOWN_LOCALES,
|
||||
e));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert a set of localizations for a single locale. This will amend the
|
||||
* existing localizations for the given locale.
|
||||
*
|
||||
* @param {string} locale
|
||||
* The locale that the localizations should be added to.
|
||||
* @param {!Map.<string, string>} localizations
|
||||
* A mapping of id to localized text that should used to modify the internal
|
||||
* collection of localizations.
|
||||
* @param {shaka.ui.Localization.ConflictResolution=} conflictResolution
|
||||
* The strategy used to resolve conflicts when the id of an existing entry
|
||||
* matches the id of a new entry. Default to |USE_NEW|, where the new
|
||||
* entry will replace the old entry.
|
||||
* @return {!shaka.ui.Localization}
|
||||
* Returns |this| so that calls can be chained.
|
||||
* @export
|
||||
*/
|
||||
insert(locale, localizations, conflictResolution) {
|
||||
const Class = shaka.ui.Localization;
|
||||
const ConflictResolution = shaka.ui.Localization.ConflictResolution;
|
||||
const FakeEvent = shaka.util.FakeEvent;
|
||||
|
||||
// Normalize the locale so that matching will be easier.
|
||||
locale = shaka.util.LanguageUtils.normalize(locale);
|
||||
|
||||
// Default |conflictResolution| to |USE_NEW| if it was not given. Doing it
|
||||
// here because it would create too long of a parameter list.
|
||||
if (conflictResolution === undefined) {
|
||||
conflictResolution = ConflictResolution.USE_NEW;
|
||||
}
|
||||
|
||||
// Make sure we have an entry for the locale because we are about to
|
||||
// write to it.
|
||||
const table = this.localizations_.get(locale) || new Map();
|
||||
localizations.forEach((value, id) => {
|
||||
// Set the value if we don't have an old value or if we are to replace
|
||||
// the old value with the new value.
|
||||
if (!table.has(id) || conflictResolution == ConflictResolution.USE_NEW) {
|
||||
table.set(id, value);
|
||||
}
|
||||
});
|
||||
this.localizations_.set(locale, table);
|
||||
|
||||
// The data we use to make our map may have changed, update the map we pull
|
||||
// data from.
|
||||
this.updateCurrentMap_();
|
||||
|
||||
this.events_.dispatchEvent(new FakeEvent(Class.LOCALE_UPDATED));
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the value under each key in |dictionary| to the resolved value.
|
||||
* Convenient for apps with some kind of data binding system.
|
||||
*
|
||||
* Equivalent to:
|
||||
* for (const key of dictionary.keys()) {
|
||||
* dictionary.set(key, localization.resolve(key));
|
||||
* }
|
||||
*
|
||||
* @param {!Map.<string, string>} dictionary
|
||||
* @export
|
||||
*/
|
||||
resolveDictionary(dictionary) {
|
||||
for (const key of dictionary.keys()) {
|
||||
// Since we are not changing what keys are in the map, it is safe to
|
||||
// update the map while iterating it.
|
||||
dictionary.set(key, this.resolve(key));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Request the localized string under the given id. If there is no localized
|
||||
* version of the string, then the fallback localization will be given
|
||||
* ("en" version). If there is no fallback localization, a non-null empty
|
||||
* string will be returned.
|
||||
*
|
||||
* @param {string} id The id for the localization entry.
|
||||
* @return {string}
|
||||
* @export
|
||||
*/
|
||||
resolve(id) {
|
||||
const Class = shaka.ui.Localization;
|
||||
const FakeEvent = shaka.util.FakeEvent;
|
||||
|
||||
/** @type {string} */
|
||||
const result = this.currentMap_.get(id);
|
||||
|
||||
// If we have a result, it means that it was found in either the current
|
||||
// locale or one of the fall-backs.
|
||||
if (result) {
|
||||
return result;
|
||||
}
|
||||
|
||||
// Since we could not find the result, it means it is missing from a large
|
||||
// number of locales. Since we don't know which ones we actually checked,
|
||||
// just tell them the preferred locale.
|
||||
|
||||
/** @type {shaka.ui.Localization.UnknownLocalizationEvent} */
|
||||
const e = {
|
||||
// Make a copy to avoid leaking references.
|
||||
'locales': Array.from(this.currentLocales_),
|
||||
'missing': id,
|
||||
};
|
||||
|
||||
this.events_.dispatchEvent(new FakeEvent(Class.UNKNOWN_LOCALIZATION, e));
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
updateCurrentMap_() {
|
||||
const LanguageUtils = shaka.util.LanguageUtils;
|
||||
|
||||
/** @type {!Map.<string, !Map.<string, string>>} */
|
||||
const localizations = this.localizations_;
|
||||
/** @type {string} */
|
||||
const fallbackLocale = this.fallbackLocale_;
|
||||
/** @type {!Iterable.<string>} */
|
||||
const preferredLocales = this.currentLocales_;
|
||||
|
||||
/**
|
||||
* We want to create a single map that gives us the best possible responses
|
||||
* for the current locale. To do this, we will go through be loosest
|
||||
* matching locales to the best matching locales. By the time we finish
|
||||
* flattening the maps, the best result will be left under each key.
|
||||
*
|
||||
* Get the locales we should use in order of preference. For example with
|
||||
* preferred locales of "elvish-WOODLAND" and "dwarfish-MOUNTAIN" and a
|
||||
* fallback of "common-HUMAN", this would look like:
|
||||
*
|
||||
* new Set([
|
||||
* // Preference 1
|
||||
* 'elvish-WOODLAND',
|
||||
* // Preference 1 Base
|
||||
* 'elvish',
|
||||
* // Preference 1 Siblings
|
||||
* 'elvish-WOODLAND', 'elvish-WESTWOOD', 'elvish-MARSH,
|
||||
* // Preference 2
|
||||
* 'dwarfish-MOUNTAIN',
|
||||
* // Preference 2 base
|
||||
* 'dwarfish',
|
||||
* // Preference 2 Siblings
|
||||
* 'dwarfish-MOUNTAIN', 'dwarfish-NORTH', "dwarish-SOUTH",
|
||||
* // Fallback
|
||||
* 'common-HUMAN',
|
||||
* ])
|
||||
*
|
||||
* @type {!Set.<string>}
|
||||
*/
|
||||
const localeOrder = new Set();
|
||||
|
||||
for (const locale of preferredLocales) {
|
||||
localeOrder.add(locale);
|
||||
localeOrder.add(LanguageUtils.getBase(locale));
|
||||
|
||||
const siblings = shaka.util.Iterables.filter(
|
||||
localizations.keys(),
|
||||
(other) => LanguageUtils.areSiblings(other, locale));
|
||||
|
||||
// Sort the siblings so that they will always appear in the same order
|
||||
// regardless of the order of |localizations|.
|
||||
siblings.sort();
|
||||
for (const locale of siblings) { localeOrder.add(locale); }
|
||||
|
||||
const children = shaka.util.Iterables.filter(
|
||||
localizations.keys(),
|
||||
(other) => LanguageUtils.getBase(other) == locale);
|
||||
|
||||
// Sort the children so that they will always appear in the same order
|
||||
// regardless of the order of |localizations|.
|
||||
children.sort();
|
||||
for (const locale of children) { localeOrder.add(locale); }
|
||||
}
|
||||
|
||||
// Finally we add our fallback (something that should have all expected
|
||||
// entries).
|
||||
localeOrder.add(fallbackLocale);
|
||||
|
||||
// Add all the sibling maps.
|
||||
const mergeOrder = [];
|
||||
for (const locale of localeOrder) {
|
||||
const map = localizations.get(locale);
|
||||
if (map) { mergeOrder.push(map); }
|
||||
}
|
||||
|
||||
// We need to reverse the merge order. We build the order based on most
|
||||
// preferred to least preferred. However, the merge will work in the
|
||||
// opposite order so we must reverse our maps so that the most preferred
|
||||
// options will be applied last.
|
||||
mergeOrder.reverse();
|
||||
|
||||
// Merge all the options into our current map.
|
||||
this.currentMap_.clear();
|
||||
for (const map of mergeOrder) {
|
||||
map.forEach((value, key) => {
|
||||
this.currentMap_.set(key, value);
|
||||
});
|
||||
}
|
||||
|
||||
// Go through every key we have and see if any preferred locales are
|
||||
// missing entries. This will allow app developers to find holes in their
|
||||
// localizations.
|
||||
|
||||
/** @type {!Iterable.<string>} */
|
||||
const allKeys = this.currentMap_.keys();
|
||||
|
||||
/** @type {!Set.<string>} */
|
||||
const missing = new Set();
|
||||
|
||||
for (const locale of this.currentLocales_) {
|
||||
// Make sure we have a non-null map. The diff will be easier that way.
|
||||
const map = this.localizations_.get(locale) || new Map();
|
||||
shaka.ui.Localization.findMissingKeys_(map, allKeys, missing);
|
||||
}
|
||||
|
||||
if (missing.size > 0) {
|
||||
/** @type {shaka.ui.Localization.MissingLocalizationsEvent} */
|
||||
const e = {
|
||||
// Make a copy of the preferred locales to avoid leaking references.
|
||||
locales: Array.from(preferredLocales),
|
||||
// Because most people like arrays more than sets, convert the set to
|
||||
// an array.
|
||||
missing: Array.from(missing),
|
||||
};
|
||||
|
||||
this.events_.dispatchEvent(new shaka.util.FakeEvent(
|
||||
shaka.ui.Localization.MISSING_LOCALIZATIONS,
|
||||
e));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Go through a map and add all the keys that are in |keys| but not in
|
||||
* |map| to |missing|.
|
||||
*
|
||||
* @param {!Map.<string, string>} map
|
||||
* @param {!Iterable.<string>} keys
|
||||
* @param {!Set.<string>} missing
|
||||
* @private
|
||||
*/
|
||||
static findMissingKeys_(map, keys, missing) {
|
||||
for (const key of keys) {
|
||||
// Check if the value is missing so that we are sure that it does not
|
||||
// have a value. We get the value and not just |has| so that a null or
|
||||
// empty string will fail this check.
|
||||
if (!map.get(key)) {
|
||||
missing.add(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* An enum for how the localization system should resolve conflicts between old
|
||||
* translations and new translations.
|
||||
*
|
||||
* @enum {number}
|
||||
* @export
|
||||
*/
|
||||
shaka.ui.Localization.ConflictResolution = {
|
||||
'USE_OLD': 0,
|
||||
'USE_NEW': 1,
|
||||
};
|
||||
|
||||
/**
|
||||
* The event name for when locales were requested, but we could not find any
|
||||
* entries for them. The localization system will continue to use the closest
|
||||
* matches it has.
|
||||
*
|
||||
* @const {string}
|
||||
*/
|
||||
shaka.ui.Localization.UNKNOWN_LOCALES = 'unknown-locales';
|
||||
|
||||
/**
|
||||
* The event name for when an entry could not be found in the preferred locale,
|
||||
* related locales, or the fallback locale.
|
||||
*
|
||||
* @const {string}
|
||||
*/
|
||||
shaka.ui.Localization.UNKNOWN_LOCALIZATION = 'unknown-localization';
|
||||
|
||||
/**
|
||||
* The event name for when entries are missing from the user's preferred
|
||||
* locale, but we were able to find an entry in a related locale or the fallback
|
||||
* locale.
|
||||
*
|
||||
* @const {string}
|
||||
*/
|
||||
shaka.ui.Localization.MISSING_LOCALIZATIONS = 'missing-localizations';
|
||||
|
||||
/**
|
||||
* The event name for when a new locale has been requested and any previously
|
||||
* resolved values should be updated.
|
||||
*
|
||||
* @const {string}
|
||||
*/
|
||||
shaka.ui.Localization.LOCALE_CHANGED = 'locale-changed';
|
||||
|
||||
/**
|
||||
* The event name for when |insert| was called and it changed entries that could
|
||||
* affect previously resolved values.
|
||||
*
|
||||
* @const {string}
|
||||
*/
|
||||
shaka.ui.Localization.LOCALE_UPDATED = 'locale-updated';
|
||||
|
||||
/**
|
||||
* @typedef {{
|
||||
* 'locales': !Array.<string>
|
||||
* }}
|
||||
*
|
||||
* @property {!Array.<string>} locales
|
||||
* The locales that the user wanted but could not be found.
|
||||
* @exportDoc
|
||||
*/
|
||||
shaka.ui.Localization.UnknownLocalesEvent;
|
||||
|
||||
/**
|
||||
* @typedef {{
|
||||
* 'locales': !Array.<string>,
|
||||
* 'missing': string
|
||||
* }}
|
||||
*
|
||||
* @property {!Array.<string>} locales
|
||||
* The locales that the user wanted.
|
||||
* @property {string} missing
|
||||
* The id of the unknown entry.
|
||||
* @exportDoc
|
||||
*/
|
||||
shaka.ui.Localization.UnknownLocalizationEvent;
|
||||
|
||||
/**
|
||||
* @typedef {{
|
||||
* 'locales': !Array.<string>,
|
||||
* 'missing': !Array.<string>
|
||||
* }}
|
||||
*
|
||||
* @property {string} locale
|
||||
* The locale that the user wanted.
|
||||
* @property {!Array.<string>} missing
|
||||
* The ids of the missing entries.
|
||||
* @exportDoc
|
||||
*/
|
||||
shaka.ui.Localization.MissingLocalizationsEvent;
|
||||
@@ -0,0 +1,4 @@
|
||||
<svg fill="#000000" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M6 19h4V5H6v14zm8-14v14h4V5h-4z"/>
|
||||
<path d="M0 0h24v24H0z" fill="none"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 195 B |
@@ -0,0 +1,4 @@
|
||||
<svg fill="#000000" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M8 5v14l11-7z"/>
|
||||
<path d="M0 0h24v24H0z" fill="none"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 177 B |
@@ -0,0 +1,250 @@
|
||||
/**
|
||||
* @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.
|
||||
*/
|
||||
|
||||
|
||||
goog.provide('shaka.ui.Overlay');
|
||||
|
||||
goog.require('goog.asserts');
|
||||
goog.require('shaka.polyfill.installAll');
|
||||
goog.require('shaka.ui.Controls');
|
||||
|
||||
|
||||
/**
|
||||
* @param {!shaka.Player} player
|
||||
* @param {!HTMLElement} videoContainer
|
||||
* @param {!HTMLMediaElement} video
|
||||
* @param {!Object=} config This should follow the form of
|
||||
* {@link shaka.extern.UIConfiguration}, but you may omit
|
||||
* any field you do not wish to change.
|
||||
* @constructor
|
||||
* @export
|
||||
*/
|
||||
shaka.ui.Overlay = function(player, videoContainer, video, config) {
|
||||
/** @private {!shaka.Player} */
|
||||
this.player_ = player;
|
||||
|
||||
/** @private {!shaka.extern.UIConfiguration} */
|
||||
this.config_ = this.defaultConfig_();
|
||||
|
||||
if (config) {
|
||||
shaka.util.ConfigUtils.mergeConfigObjects(
|
||||
this.config_, config, this.defaultConfig_(),
|
||||
/* overrides (only used for player config)*/ {}, /* path */ '');
|
||||
}
|
||||
|
||||
// If a cast receiver app id has been given, add a cast button to the UI
|
||||
if (this.config_.castReceiverAppId &&
|
||||
!this.config_.overflowMenuButtons.includes('cast')) {
|
||||
this.config_.overflowMenuButtons.push('cast');
|
||||
}
|
||||
|
||||
/** @private {!shaka.ui.Controls} */
|
||||
this.controls_ = new shaka.ui.Controls(
|
||||
player, videoContainer, video, this.config_);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {!shaka.Player}
|
||||
* @export
|
||||
*/
|
||||
shaka.ui.Overlay.prototype.getPlayer = function() {
|
||||
return this.player_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {!shaka.ui.Controls}
|
||||
* @export
|
||||
*/
|
||||
shaka.ui.Overlay.prototype.getControls = function() {
|
||||
return this.controls_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Enable or disable the custom controls.
|
||||
*
|
||||
* @param {boolean} enabled
|
||||
* @export
|
||||
*/
|
||||
shaka.ui.Overlay.prototype.setEnabled = function(enabled) {
|
||||
this.controls_.setEnabledShakaControls(enabled);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {!shaka.extern.UIConfiguration}
|
||||
* @private
|
||||
*/
|
||||
shaka.ui.Overlay.prototype.defaultConfig_ = function() {
|
||||
return {
|
||||
controlPanelElements: [
|
||||
'time_and_duration',
|
||||
'mute',
|
||||
'volume',
|
||||
'fullscreen',
|
||||
'overflow_menu',
|
||||
],
|
||||
overflowMenuButtons: [
|
||||
'captions',
|
||||
'quality',
|
||||
'language',
|
||||
],
|
||||
addSeekBar: true,
|
||||
adaptPlayButtonSize: true,
|
||||
castReceiverAppId: '',
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
shaka.ui.Overlay.scanPageForShakaElements_ = function() {
|
||||
// Install built-in polyfills to patch browser incompatibilities.
|
||||
shaka.polyfill.installAll();
|
||||
// Check to see if the browser supports the basic APIs Shaka needs.
|
||||
if (!shaka.Player.isBrowserSupported()) {
|
||||
shaka.log.error('Shaka Player does not support this browser. ' +
|
||||
'Please see https://tinyurl.com/y7s4j9tr for the list of ' +
|
||||
'supported browsers.');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Look for elements marked 'data-shaka-player-container'
|
||||
// on the page. These will be used to create our default
|
||||
// UI.
|
||||
const containers = document.querySelectorAll(
|
||||
'[data-shaka-player-container]');
|
||||
// Look for elements marked 'data-shaka-player'. They will
|
||||
// either be used in our default UI or with native browser
|
||||
// controls.
|
||||
const videos = document.querySelectorAll(
|
||||
'[data-shaka-player]');
|
||||
|
||||
if (!videos.length && !containers.length) {
|
||||
// No elements have been tagged with shaka attributes.
|
||||
} else if (videos.length && !containers.length) {
|
||||
// Just the video elements were provided.
|
||||
for (let i = 0; i < videos.length; i++) {
|
||||
const video = videos[i];
|
||||
video.classList.add('video');
|
||||
goog.asserts.assert(video.tagName.toLowerCase() == 'video',
|
||||
'Should be a video element!');
|
||||
|
||||
const container = document.createElement('div');
|
||||
const videoParent = video.parentElement;
|
||||
videoParent.replaceChild(container, video);
|
||||
container.appendChild(video);
|
||||
|
||||
let castAppId = '';
|
||||
|
||||
// If cast receiver application id was provided, pass it to the
|
||||
// UI constructor.
|
||||
if (video['dataset'] && video['dataset']['shakaPlayerCastReceiverId']) {
|
||||
castAppId = video['dataset']['shakaPlayerCastReceiverId'];
|
||||
}
|
||||
|
||||
const videoAsMediaElement = /** @type {!HTMLMediaElement} */ (video);
|
||||
const ui = shaka.ui.Overlay.createUI_(
|
||||
/** @type {!HTMLElement} */ (container),
|
||||
videoAsMediaElement,
|
||||
{castReceiverAppId: castAppId});
|
||||
|
||||
if (videoAsMediaElement.controls) {
|
||||
ui.getControls().setEnabledNativeControls(true);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (let i = 0; i < containers.length; i++) {
|
||||
const container = containers[i];
|
||||
goog.asserts.assert(container.tagName.toLowerCase() == 'div',
|
||||
'Container should be a div!');
|
||||
|
||||
let castAppId = '';
|
||||
|
||||
// Cast receiver id can be specified on either container or video.
|
||||
// It should not be provided on both. If it was, we will use the last
|
||||
// one we saw.
|
||||
if (container['dataset'] &&
|
||||
container['dataset']['shakaPlayerCastReceiverId']) {
|
||||
castAppId = container['dataset']['shakaPlayerCastReceiverId'];
|
||||
}
|
||||
|
||||
let video = null;
|
||||
for (let j = 0; j < videos.length; j++) {
|
||||
goog.asserts.assert(videos[j].tagName.toLowerCase() == 'video',
|
||||
'Should be a video element!');
|
||||
if (videos[j].parentElement == container) {
|
||||
video = videos[j];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!video) {
|
||||
video = document.createElement('video');
|
||||
container.appendChild(video);
|
||||
}
|
||||
|
||||
if (video['dataset'] && video['dataset']['shakaPlayerCastReceiverId']) {
|
||||
castAppId = video['dataset']['shakaPlayerCastReceiverId'];
|
||||
}
|
||||
shaka.ui.Overlay.createUI_(/** @type {!HTMLElement} */ (container),
|
||||
/** @type {!HTMLMediaElement} */ (video),
|
||||
{castReceiverAppId: castAppId});
|
||||
}
|
||||
}
|
||||
|
||||
// After scanning the page for elements, fire the "loaded" event. This will
|
||||
// let apps know they can use the UI library programatically now, even if they
|
||||
// didn't have any Shaka-related elements declared in their HTML.
|
||||
|
||||
// "Event" is not constructable on IE, so we use this CustomEvent pattern.
|
||||
const uiLoadedEvent = /** @type {!CustomEvent} */(
|
||||
document.createEvent('CustomEvent'));
|
||||
uiLoadedEvent.initCustomEvent('shaka-ui-loaded', false, false, null);
|
||||
|
||||
document.dispatchEvent(uiLoadedEvent);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {!HTMLElement} container
|
||||
* @param {!HTMLMediaElement} video
|
||||
* @param {!Object} config (Possibly partial) config in the form of
|
||||
* {@link shaka.extern.UIConfiguration}
|
||||
* @return {!shaka.ui.Overlay}
|
||||
* @private
|
||||
*/
|
||||
shaka.ui.Overlay.createUI_ = function(container, video, config) {
|
||||
const player = new shaka.Player(video);
|
||||
const ui = new shaka.ui.Overlay(player, container, video, config);
|
||||
container['ui'] = ui;
|
||||
video['ui'] = ui;
|
||||
return ui;
|
||||
};
|
||||
|
||||
|
||||
if (document.readyState == 'complete') {
|
||||
// Don't fire this event synchronously. In a compiled bundle, the "shaka"
|
||||
// namespace might not be exported to the window until after this point.
|
||||
Promise.resolve().then(shaka.ui.Overlay.scanPageForShakaElements_);
|
||||
} else {
|
||||
window.addEventListener('load', shaka.ui.Overlay.scanPageForShakaElements_);
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
/**
|
||||
* @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.
|
||||
*/
|
||||
|
||||
|
||||
goog.provide('shaka.ui.Utils');
|
||||
|
||||
goog.require('goog.asserts');
|
||||
|
||||
|
||||
/**
|
||||
* @param {!HTMLElement} element
|
||||
* @param {string} className
|
||||
* @return {!HTMLElement}
|
||||
* @export
|
||||
*/
|
||||
shaka.ui.Utils.getFirstDescendantWithClassName = function(element, className) {
|
||||
let descendant = shaka.ui.Utils.getDescendantIfExists(element, className);
|
||||
goog.asserts.assert(descendant != null, 'Should not be null!');
|
||||
|
||||
return descendant;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {!HTMLElement} element
|
||||
* @param {string} className
|
||||
* @return {?HTMLElement}
|
||||
*/
|
||||
shaka.ui.Utils.getDescendantIfExists = function(element, className) {
|
||||
let childrenWithClassName = element.getElementsByClassName(className);
|
||||
if (childrenWithClassName.length) {
|
||||
return /** @type {!HTMLElement} */ (childrenWithClassName[0]);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Return true if the content is Transport Stream.
|
||||
* Used to decide if caption button is shown all the time in the demo,
|
||||
* and whether to show 'Default Text' as a Text Track option.
|
||||
*
|
||||
* @param {shaka.Player} player
|
||||
* @return {boolean}
|
||||
*/
|
||||
shaka.ui.Utils.isTsContent = function(player) {
|
||||
let activeTracks = player.getVariantTracks().filter(function(track) {
|
||||
return track.active == true;
|
||||
});
|
||||
let activeTrack = activeTracks[0];
|
||||
if (activeTrack) {
|
||||
return activeTrack.mimeType == 'video/mp2t';
|
||||
}
|
||||
return false;
|
||||
};
|
||||
Reference in New Issue
Block a user