diff --git a/.github/workflows/custom-actions/prep-for-appspot/action.yaml b/.github/workflows/custom-actions/prep-for-appspot/action.yaml
new file mode 100644
index 0000000000..19968a977f
--- /dev/null
+++ b/.github/workflows/custom-actions/prep-for-appspot/action.yaml
@@ -0,0 +1,56 @@
+name: Prep Appspot Deployment
+
+description: |
+ A reusable action that prepares the sources to deploy the Shaka Player Demo
+ to appspot. Assumes that the source is already checked out.
+
+runs:
+ using: composite
+ steps:
+ - name: Install dependencies
+ shell: bash
+ run: npm ci
+
+ - name: Build Shaka Player
+ shell: bash
+ run: python build/all.py
+
+ - name: Extract git version
+ shell: bash
+ run: |
+ (
+ cd build
+ python3 -c 'from shakaBuildHelpers import git_version; print(git_version())'
+ ) > demo-version
+
+ - name: Tag the Player version
+ shell: bash
+ run: |
+ DEMO_VERSION="$(cat demo-version)-uncompiled"
+ sed \
+ -i lib/player.js \
+ -e "s/\(shaka.Player.version\) = .*/\1 = '$DEMO_VERSION';/"
+ rm demo-version
+
+ - name: Backup demo node modules
+ shell: bash
+ run: |
+ DEMO_NODE_MODULES=$(jq -r .shakaCustom.demoDeps[] package.json)
+ tar cf demo-modules.tar \
+ $(echo "$DEMO_NODE_MODULES" | sed -e 's@^@node_modules/@')
+
+ - name: Delete unneeded files
+ shell: bash
+ run: rm -rf .git .github build externs test node_modules
+
+ - name: Restore demo node modules
+ shell: bash
+ run: |
+ tar xf demo-modules.tar
+ rm demo-modules.tar
+
+ - name: Install App Engine code
+ shell: bash
+ run: |
+ mv app-engine/shaka-player-demo/* .
+ rm README.md
diff --git a/.github/workflows/demo-version-index.yaml b/.github/workflows/demo-version-index.yaml
new file mode 100644
index 0000000000..300a6621cd
--- /dev/null
+++ b/.github/workflows/demo-version-index.yaml
@@ -0,0 +1,29 @@
+name: Deploy Demo Version Index
+
+on:
+ workflow_dispatch:
+ # Allows for manual triggering.
+ push:
+ branches:
+ - main
+ paths:
+ - .github/workflows/demo-version-index.yaml
+ - app-engine/demo-version-index/**
+
+jobs:
+ appspot:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v2
+
+ - uses: google-github-actions/auth@v0
+ with:
+ credentials_json: '${{ secrets.APPENGINE_DEPLOY_KEY }}'
+
+ - uses: google-github-actions/deploy-appengine@v0
+ with:
+ project_id: shaka-player-demo
+ version: index
+ working_directory: app-engine/demo-version-index/
+ promote: false
+
diff --git a/.github/workflows/nightly-demo.yaml b/.github/workflows/nightly-demo.yaml
new file mode 100644
index 0000000000..84681b9c14
--- /dev/null
+++ b/.github/workflows/nightly-demo.yaml
@@ -0,0 +1,42 @@
+name: Deploy Nightly Demo
+
+on:
+ workflow_dispatch:
+ # Allows for manual triggering.
+ schedule:
+ # Run every night at midnight PST / 8am UTC.
+ - cron: '0 8 * * *'
+
+jobs:
+ appspot:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v2
+ with:
+ fetch-depth: 0
+ persist-credentials: false
+
+ - uses: actions/setup-node@v1
+ with:
+ node-version: 16
+ registry-url: 'https://registry.npmjs.org'
+
+ # The nightly demo has its own receiver app ID that points to the nightly
+ # demo itself for the receiver side.
+ - name: Override Cast Receiver App ID
+ run: |
+ sed \
+ -i demo/index.html \
+ -e 's/\(data-shaka-player-cast-receiver-id\)="[^"]*"/\1="07AEE832"/'
+
+ - uses: ./.github/workflows/custom-actions/prep-for-appspot
+
+ - uses: google-github-actions/auth@v0
+ with:
+ credentials_json: '${{ secrets.APPENGINE_DEPLOY_KEY }}'
+
+ - uses: google-github-actions/deploy-appengine@v0
+ with:
+ project_id: shaka-player-demo
+ version: nightly
+ promote: false
diff --git a/.github/workflows/release-please.yaml b/.github/workflows/release-please.yaml
index 84b9502145..e8bc83e69b 100644
--- a/.github/workflows/release-please.yaml
+++ b/.github/workflows/release-please.yaml
@@ -93,4 +93,51 @@ jobs:
file_glob: true
overwrite: true
- # TODO: Publish to appspot
+ appspot:
+ runs-on: ubuntu-latest
+ needs: release
+ if: ${{ needs.release.outputs.release_created }}
+ steps:
+ - uses: actions/checkout@v2
+ with:
+ fetch-depth: 0
+ persist-credentials: false
+
+ - uses: actions/setup-node@v1
+ with:
+ node-version: 16
+ registry-url: 'https://registry.npmjs.org'
+
+ - uses: ./.github/workflows/custom-actions/prep-for-appspot
+
+ - uses: google-github-actions/auth@v0
+ with:
+ credentials_json: '${{ secrets.APPENGINE_DEPLOY_KEY }}'
+
+ - name: Compute appspot subdomain
+ run: |
+ # This is the same as the version tag, but with dots replaced by
+ # dashes. For example, v3.2.2 would have the subdomain v3-2-2.
+ APPSPOT_SUBDOMAIN=$( echo ${{ needs.release.outputs.tag_name }} | sed -e 's/\./-/' )
+ echo APPSPOT_SUBDOMAIN=$APPSPOT_SUBDOMAIN >> $GITHUB_ENV
+
+ - name: Compute appspot promotion
+ run: |
+ # "Promoting" an appspot deployment makes it the default which shows
+ # up on shaka-player-demo.appspot.com (no subdomain). This should be
+ # done for the latest release version from the latest release branch.
+ RELEASE_TAGS=$(git tag | grep ^v[0-9] | grep -Ev -- '-(master|main)')
+ LATEST_RELEASE=$(echo "$RELEASE_TAGS" | sort --version-sort | tail -1)
+ TAG_NAME=${{ needs.release.outputs.tag_name }}
+
+ if [[ "$TAG_NAME" == "$LATEST_RELEASE" ]]; then
+ echo APPSPOT_PROMOTE=true >> $GITHUB_ENV
+ else
+ echo APPSPOT_PROMOTE=false >> $GITHUB_ENV
+ fi
+
+ - uses: google-github-actions/deploy-appengine@v0
+ with:
+ project_id: shaka-player-demo
+ version: ${{ env.APPSPOT_SUBDOMAIN }}
+ promote: ${{ env.APPSPOT_PROMOTE }}
diff --git a/app-engine/README.md b/app-engine/README.md
new file mode 100644
index 0000000000..0c2acbd215
--- /dev/null
+++ b/app-engine/README.md
@@ -0,0 +1,9 @@
+# Google App Engine Code
+
+This folder contains source code for the services we run on Google App Engine
+(appspot.com).
+
+ - shaka-player-demo: Our hosted demo on https://shaka-player-demo.appspot.com/
+
+ - demo-version-index: An index of Shaka Player releases and demos, hosted at
+ https://index-dot-shaka-player-demo.appspot.com/
diff --git a/app-engine/demo-version-index/README.md b/app-engine/demo-version-index/README.md
new file mode 100644
index 0000000000..bc760d854d
--- /dev/null
+++ b/app-engine/demo-version-index/README.md
@@ -0,0 +1,14 @@
+# Google App Engine Version Index
+
+This folder contains everything necessary to host an index of Shaka Player
+releases and demos at https://index-dot-shaka-player-demo.appspot.com/
+
+ - app.yaml: App Engine config file. Defines the runtime (Python 3).
+
+ - main.py: A python service that queries available versions and generates the
+ index from a template.
+
+ - requirements.txt: Used by App Engine to install the necessary Python server
+ requirements (Flask, App Engine API).
+
+ - templates/index.html: A Jinja2 template used to generate the index HTML.
diff --git a/app-engine/demo-version-index/app.yaml b/app-engine/demo-version-index/app.yaml
new file mode 100644
index 0000000000..017d92393c
--- /dev/null
+++ b/app-engine/demo-version-index/app.yaml
@@ -0,0 +1,2 @@
+runtime: python39
+default_expiration: 1s
diff --git a/app-engine/demo-version-index/main.py b/app-engine/demo-version-index/main.py
new file mode 100644
index 0000000000..fe18d1a0f0
--- /dev/null
+++ b/app-engine/demo-version-index/main.py
@@ -0,0 +1,162 @@
+# Shaka Player Version Index - Appspot Entrypoint
+# Copyright 2022 Google LLC
+# SPDX-License-Identifier: Apache-2.0
+
+# Generate an index of Shaka Player versions on appspot.
+
+import collections
+import google.appengine.api.modules
+import jinja2
+import os
+import random
+import re
+
+from flask import Flask, render_template
+
+DEMO_URL_TEMPLATE = 'https://{0}-dot-shaka-player-demo.appspot.com/'
+
+# In Google Hosted Libraries
+HOSTED_URL_TEMPLATE = 'https://ajax.googleapis.com/ajax/libs/shaka-player/{0}/shaka-player.{1}'
+
+# Before Google Hosted Libraries, v1 appspot URLs
+V1_URL_TEMPLATE = 'https://{0}-dot-shaka-player-demo.appspot.com/shaka-player.{1}'
+# Before Google Hosted Libraries, v2 appspot URLs
+V2_URL_TEMPLATE = 'https://{0}-dot-shaka-player-demo.appspot.com/dist/shaka-player.{1}'
+
+def version_to_demo_url(v):
+ return DEMO_URL_TEMPLATE.format(v.replace('.', '-'))
+
+def version_to_lib_url(v):
+ if v == 'nightly':
+ return V2_URL_TEMPLATE.format(v, 'compiled.js')
+ elif (version_key(v) == version_key('v1.6.5') or
+ version_key(v) >= version_key('v2.0.6')):
+ return HOSTED_URL_TEMPLATE.format(v.replace('v', ''), 'compiled.js')
+ elif version_key(v) >= version_key('v2.0.0-beta'):
+ return V2_URL_TEMPLATE.format(v.replace('.', '-'), 'compiled.js')
+ else:
+ return V1_URL_TEMPLATE.format(v.replace('.', '-'), 'compiled.js')
+
+def version_to_ui_lib_url(v):
+ if v == 'nightly':
+ return V2_URL_TEMPLATE.format(v, 'ui.js')
+ elif version_key(v) >= version_key('v2.5.0'):
+ return HOSTED_URL_TEMPLATE.format(v.replace('v', ''), 'ui.js')
+ else:
+ return None
+
+def version_to_lib_externs_url(v):
+ if v == 'nightly':
+ return V2_URL_TEMPLATE.format(v, 'compiled.externs.js')
+ elif version_key(v) >= version_key('v2.0.6'):
+ return HOSTED_URL_TEMPLATE.format(v.replace('v', ''), 'compiled.externs.js')
+ else:
+ return None
+
+def version_to_ui_lib_externs_url(v):
+ if v == 'nightly':
+ return V2_URL_TEMPLATE.format(v, 'ui.externs.js')
+ elif version_key(v) >= version_key('v2.5.0'):
+ return HOSTED_URL_TEMPLATE.format(v.replace('v', ''), 'ui.externs.js')
+ else:
+ return None
+
+def version_to_lib_defs_url(v):
+ if v == 'nightly':
+ return V2_URL_TEMPLATE.format(v, 'compiled.d.ts')
+ elif version_key(v) >= version_key('v3.0.6'):
+ return HOSTED_URL_TEMPLATE.format(v.replace('v', ''), 'compiled.d.ts')
+ else:
+ return None
+
+def version_to_ui_lib_defs_url(v):
+ if v == 'nightly':
+ return V2_URL_TEMPLATE.format(v, 'ui.d.ts')
+ elif version_key(v) >= version_key('v3.0.6'):
+ return HOSTED_URL_TEMPLATE.format(v.replace('v', ''), 'ui.d.ts')
+ else:
+ return None
+
+def version_to_metadata(v):
+ return {
+ 'version': v,
+ 'best': False, # Corrected later in another loop
+ 'demo': version_to_demo_url(v),
+ 'lib': version_to_lib_url(v),
+ 'ui_lib': version_to_ui_lib_url(v),
+ 'lib_externs': version_to_lib_externs_url(v),
+ 'ui_lib_externs': version_to_ui_lib_externs_url(v),
+ 'lib_defs': version_to_lib_defs_url(v),
+ 'ui_lib_defs': version_to_ui_lib_defs_url(v),
+ }
+
+def is_release_version(name):
+ return re.match(r'v\d+-\d+-\d+(?:-.+)?', name)
+
+def appengine_version_to_package_version(version):
+ # Replace the first two dashes with dots. More dashes indicate a prerelease
+ # version, as seen in "v2.0.0-beta3".
+ return version.replace('-', '.', 2)
+
+def version_key(version):
+ if version == 'nightly':
+ # A false version number for nightly, greater than any actual release
+ # version.
+ return [float('inf')]
+
+ assert version[0] == 'v'
+ main_version, _, suffix = version[1:].partition('-')
+ if not suffix:
+ suffix = '}}}' # this puts main releases after prerelease versions
+ version_tuple = [int(x) for x in main_version.split('.')]
+ return version_tuple + [suffix]
+
+def get_appengine_versions():
+ if os.getenv('SERVER_SOFTWARE', '').startswith('Google App Engine/'):
+ # NOTE: this doesn't return anything useful in a local dev server.
+ return google.appengine.api.modules.modules.get_versions()
+
+ # For a local dev server, fake it so we can test sorting.
+ fake_versions = [
+ 'v1-6-0', 'v1-6-1', 'v1-6-2', 'v1-6-3', 'v1-6-4', 'v1-6-5',
+ 'v2-0-0-beta', 'v2-0-0-beta2', 'v2-0-0-beta3', 'v2-0-0',
+ 'v2-1-0', 'v2-1-1', 'v2-1-2',
+ 'v2-2-1', 'v2-2-2-beta', 'v2-2-2-beta2', 'v2-2-2', 'v2-2-9', 'v2-2-10',
+ ]
+ random.shuffle(fake_versions) # in-place shuffle
+ return fake_versions
+
+
+app = Flask(__name__)
+
+@app.route('/')
+def root():
+ appengine_versions = get_appengine_versions()
+ # Filter for release versions only.
+ appengine_versions = filter(is_release_version, appengine_versions)
+
+ # Now convert from appengine versions (v2-0-0) to package versions (v2.0.0).
+ versions = list(map(appengine_version_to_package_version, appengine_versions))
+ # Now sort, putting prerelease versions ahead of the corresponding release.
+ versions.sort(key=version_key)
+
+ version_metadata = collections.OrderedDict()
+ latest_by_branch = {}
+ for v in versions:
+ version_metadata[v] = version_to_metadata(v)
+
+ # Because |versions| is already sorted, we can just overwrite the entry
+ # in the latest_by_branch dictionary.
+ branch_key = tuple(version_key(v)[0:2])
+ latest_by_branch[branch_key] = v
+
+ for v in latest_by_branch.values():
+ version_metadata[v]['best'] = True
+
+ # Append nightly rather than filter for it, so that it always appears at the
+ # end of the list.
+ version_metadata['nightly'] = version_to_metadata('nightly')
+ version_metadata['nightly']['best'] = True
+
+ for i in version_metadata.values(): print(i)
+ return render_template('index.html', versions=version_metadata.values())
diff --git a/app-engine/demo-version-index/requirements.txt b/app-engine/demo-version-index/requirements.txt
new file mode 100644
index 0000000000..f1baaf589a
--- /dev/null
+++ b/app-engine/demo-version-index/requirements.txt
@@ -0,0 +1,6 @@
+# Shaka Player Version Index - Appspot Python Runtime Requirements
+# Copyright 2022 Google LLC
+# SPDX-License-Identifier: Apache-2.0
+
+Flask==2.0.3
+appengine-python-standard==0.3.1
diff --git a/app-engine/demo-version-index/templates/index.html b/app-engine/demo-version-index/templates/index.html
new file mode 100644
index 0000000000..f11d82eaaf
--- /dev/null
+++ b/app-engine/demo-version-index/templates/index.html
@@ -0,0 +1,114 @@
+
+
+
+ Shaka Player Demo - Version Index
+
+
+
+
+
+
+ Shaka Player release demos on appspot
+
+
+
+
+
+
diff --git a/app-engine/shaka-player-demo/README.md b/app-engine/shaka-player-demo/README.md
new file mode 100644
index 0000000000..0d81732eb1
--- /dev/null
+++ b/app-engine/shaka-player-demo/README.md
@@ -0,0 +1,18 @@
+# Google App Engine Demo
+
+This folder contains everything necessary to host our demo on
+https://shaka-player-demo.appspot.com/
+
+ - app.yaml: App Engine config file. Defines cache expiration and how specific
+ URLs are mapped to specific files.
+
+ - main.py: A catch-all python service to serve any non-static files. This
+ handles HTTP redirects for the root path (this is the only way to
+ do HTTP redirects in App Engine) and the poster service (which
+ returns special poster images on certain days).
+
+ - requirements.txt: Used by App Engine to install the necessary Python server
+ requirements (Flask).
+
+ - time.txt: A static file used for time sync in the client. Configured with
+ special response headers for cross-origin access.
diff --git a/app-engine/shaka-player-demo/app.yaml b/app-engine/shaka-player-demo/app.yaml
new file mode 100644
index 0000000000..e93e519724
--- /dev/null
+++ b/app-engine/shaka-player-demo/app.yaml
@@ -0,0 +1,61 @@
+# Copyright 2016 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# 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.
+
+runtime: python39
+default_expiration: 5m
+
+handlers:
+# A static file used for clock sync in the demo app
+- url: /time.txt
+ static_files: time.txt
+ upload: time.txt
+ expiration: 0
+ http_headers:
+ Access-Control-Allow-Origin: "*"
+ Access-Control-Allow-Headers: "If-Modified-Since,Range,Origin"
+ Access-Control-Expose-Headers: "Date"
+ Access-Control-Max-Age: "2592000"
+
+# These used to exist in the repo, but have been generated server-side for many
+# years now.
+- url: /assets/poster.jpg
+ script: auto
+- url: /assets/audioOnly.gif
+ script: auto
+
+# Serve favicon.ico from the demo folder.
+- url: /favicon.ico
+ secure: always
+ static_files: demo/favicon.ico
+ upload: demo/favicon.ico
+
+# For these folders, serve their indexes.
+- url: /docs/api/
+ secure: always
+ static_files: docs/api/index.html
+ upload: docs/api/index.html
+- url: /demo/
+ secure: always
+ static_files: demo/index.html
+ upload: demo/index.html
+- url: /demo/
+ secure: always
+ static_files: demo/index.html
+ upload: demo/index.html
+
+# Serve everything else directly.
+- url: /(.+)
+ secure: always
+ static_files: \1
+ upload: .+
diff --git a/app-engine/shaka-player-demo/main.py b/app-engine/shaka-player-demo/main.py
new file mode 100644
index 0000000000..97c454550d
--- /dev/null
+++ b/app-engine/shaka-player-demo/main.py
@@ -0,0 +1,95 @@
+# Shaka Player Demo - Appspot Entrypoint
+# Copyright 2022 Google LLC
+# SPDX-License-Identifier: Apache-2.0
+
+# Most of the app is served as static content. Any exceptions go here.
+
+import datetime
+import os
+
+from flask import Flask, make_response, redirect, request, send_file
+
+
+app = Flask(__name__)
+
+
+# Redirect root requests to /demo.
+@app.route('/')
+def root():
+ return redirect('/demo/', code=302)
+
+
+# A Doodle-like service to change the poster on certain days.
+# All posters are chosen based on the current date in PST.
+
+from posters import VIDEO_POSTERS, VIDEO_DEFAULT, AUDIO_POSTERS, AUDIO_DEFAULT
+
+
+# Timezone info for PST.
+# I'm ignoring DST to avoid complicating the code.
+class PST(datetime.tzinfo):
+ def utcoffset(self, dt):
+ return datetime.timedelta(hours=-8)
+
+ def dst(self, dt):
+ return datetime.timedelta(0)
+
+ def tzname(self, dt):
+ return 'PST'
+
+
+def get_poster(poster_map, default_poster):
+ now = datetime.datetime.now(PST())
+ today = (now.month, now.day)
+ today_with_year = (now.month, now.day, now.year)
+ midnight = datetime.datetime(now.year, now.month, now.day, tzinfo=PST())
+ local_expiration = midnight + datetime.timedelta(days=1)
+ max_age = (local_expiration - now).total_seconds()
+
+ # For internal debugging and testing of the poster service, we can override
+ # the date. To set an override, open /assets/poster.jpg and run this in the
+ # JS console:
+ # document.cookie="posterdate=10-31"
+ # To clear an override, run:
+ # document.cookie="posterdate="
+ override = request.cookies.get('posterdate')
+ if override:
+ override_tuple = [int(x) for x in override.split('-')]
+ print('Date override:', repr(override_tuple))
+
+ if len(override_tuple) == 3:
+ # Override includes year.
+ today_with_year = tuple(override_tuple)
+ today = tuple(override_tuple[0:2])
+ elif len(override_tuple) == 2:
+ # Override does not include year, so add the current year.
+ today_with_year = tuple(override_tuple + [now.year])
+ today = tuple(override_tuple)
+ else:
+ # Bad override format. Ignore the year, but add a response header to
+ # indicate that the override failed. For internal debugging of the
+ # poster service.
+ print('Bad date override:', override, file=sys.stderr)
+
+ # Prefer a poster for this specific year.
+ name = poster_map.get(today_with_year, poster_map.get(today, default_poster))
+ path = 'posters/%s' % name
+
+ response = make_response(
+ send_file(path, last_modified=midnight, max_age=max_age))
+
+ response.headers['Access-Control-Allow-Origin'] = '*'
+ response.headers['Access-Control-Allow-Headers'] = 'If-Modified-Since,Range'
+ response.headers['Access-Control-Expose-Headers'] = 'Date'
+ response.headers['Access-Control-Max-Age'] = '2592000'
+
+ return response
+
+
+@app.route('/assets/poster.jpg')
+def get_video_poster():
+ return get_poster(VIDEO_POSTERS, VIDEO_DEFAULT)
+
+@app.route('/assets/audioOnly.gif')
+def get_audio_poster():
+ return get_poster(AUDIO_POSTERS, AUDIO_DEFAULT)
diff --git a/app-engine/shaka-player-demo/posters.py b/app-engine/shaka-player-demo/posters.py
new file mode 100644
index 0000000000..19e49e0834
--- /dev/null
+++ b/app-engine/shaka-player-demo/posters.py
@@ -0,0 +1,110 @@
+# Shaka Player Demo - Poster Definitions
+# Copyright 2022 Google LLC
+# SPDX-License-Identifier: Apache-2.0
+
+# A Doodle-like service to change the poster on certain days.
+# All posters are chosen based on the current date in PST.
+
+# Month constants to make specific dates easier to read.
+JANUARY = 1
+FEBRUARY = 2
+MARCH = 3
+APRIL = 4
+MAY = 5
+JUNE = 6
+JULY = 7
+AUGUST = 8
+SEPTEMBER = 9
+OCTOBER = 10
+NOVEMBER = 11
+DECEMBER = 12
+
+# Special posters for certain days.
+VIDEO_POSTERS = {
+ # These are the same every year:
+ # =====
+ (FEBRUARY, 14): 'valentines.png',
+ (APRIL, 1): 'colorbars.gif',
+ (APRIL, 5): 'trek.jpg', # "First Contact Day": https://bit.ly/2CSXbxw
+ (OCTOBER, 31): 'zombie.jpg',
+ # =====
+
+ # These birthday posters changed each year:
+ (DECEMBER, 19, 2015): '1yo.jpg',
+ (DECEMBER, 19, 2016): '2yo.png',
+ (DECEMBER, 19, 2017): '3yo.png',
+
+ # After this, the birthday poster shows for a whole week:
+ (DECEMBER, 17, 2018): '4yo.png',
+ (DECEMBER, 18, 2018): '4yo.png',
+ (DECEMBER, 19, 2018): '4yo.png',
+ (DECEMBER, 20, 2018): '4yo.png',
+
+ (DECEMBER, 19, 2019): '5yo.png',
+ (DECEMBER, 20, 2019): '5yo.png',
+ (DECEMBER, 21, 2019): '5yo.png',
+ (DECEMBER, 22, 2019): '5yo.png',
+ (DECEMBER, 23, 2019): '5yo.png',
+ (DECEMBER, 24, 2019): '5yo.png',
+ (DECEMBER, 25, 2019): '5yo.png',
+
+ (DECEMBER, 19, 2020): '6yo.png',
+ (DECEMBER, 20, 2020): '6yo.png',
+ (DECEMBER, 21, 2020): '6yo.png',
+ (DECEMBER, 22, 2020): '6yo.png',
+ (DECEMBER, 23, 2020): '6yo.png',
+ (DECEMBER, 24, 2020): '6yo.png',
+ (DECEMBER, 25, 2020): '6yo.png',
+
+ (DECEMBER, 19, 2021): '7yo.png',
+ (DECEMBER, 20, 2021): '7yo.png',
+ (DECEMBER, 21, 2021): '7yo.png',
+ (DECEMBER, 22, 2021): '7yo.png',
+ (DECEMBER, 23, 2021): '7yo.png',
+ (DECEMBER, 24, 2021): '7yo.png',
+ (DECEMBER, 25, 2021): '7yo.png',
+
+ (DECEMBER, 19, 2022): '8yo.png',
+ (DECEMBER, 20, 2022): '8yo.png',
+ (DECEMBER, 21, 2022): '8yo.png',
+ (DECEMBER, 22, 2022): '8yo.png',
+ (DECEMBER, 23, 2022): '8yo.png',
+ (DECEMBER, 24, 2022): '8yo.png',
+ (DECEMBER, 25, 2022): '8yo.png',
+
+ (DECEMBER, 19, 2023): '9yo.png',
+ (DECEMBER, 20, 2023): '9yo.png',
+ (DECEMBER, 21, 2023): '9yo.png',
+ (DECEMBER, 22, 2023): '9yo.png',
+ (DECEMBER, 23, 2023): '9yo.png',
+ (DECEMBER, 24, 2023): '9yo.png',
+ (DECEMBER, 25, 2023): '9yo.png',
+
+ (DECEMBER, 19, 2024): '10yo.png',
+ (DECEMBER, 20, 2024): '10yo.png',
+ (DECEMBER, 21, 2024): '10yo.png',
+ (DECEMBER, 22, 2024): '10yo.png',
+ (DECEMBER, 23, 2024): '10yo.png',
+ (DECEMBER, 24, 2024): '10yo.png',
+ (DECEMBER, 25, 2024): '10yo.png',
+}
+
+# The default poster.
+VIDEO_DEFAULT = 'standard.png'
+
+# Special audio-only posters.
+AUDIO_POSTERS = {
+ # http://discordia.wikia.com/wiki/Chaoflux
+ (FEBRUARY, 19): 'audioRickRoll.gif',
+ # http://discordia.wikia.com/wiki/Discoflux
+ (MAY, 3): 'audioRickRoll.gif',
+ # http://discordia.wikia.com/wiki/Confuflux
+ (JULY, 15): 'audioRickRoll.gif',
+ # http://discordia.wikia.com/wiki/Bureflux
+ (SEPTEMBER, 26): 'audioRickRoll.gif',
+ # http://discordia.wikia.com/wiki/Afflux
+ (DECEMBER, 8): 'audioRickRoll.gif',
+}
+
+# The default audio-only poster.
+AUDIO_DEFAULT = 'audioOnly.gif'
diff --git a/app-engine/shaka-player-demo/posters/10yo.png b/app-engine/shaka-player-demo/posters/10yo.png
new file mode 100644
index 0000000000..975eb37b24
Binary files /dev/null and b/app-engine/shaka-player-demo/posters/10yo.png differ
diff --git a/app-engine/shaka-player-demo/posters/10yo.xcf b/app-engine/shaka-player-demo/posters/10yo.xcf
new file mode 100644
index 0000000000..bd6225b212
Binary files /dev/null and b/app-engine/shaka-player-demo/posters/10yo.xcf differ
diff --git a/app-engine/shaka-player-demo/posters/1yo.jpg b/app-engine/shaka-player-demo/posters/1yo.jpg
new file mode 100644
index 0000000000..6bd52b8060
Binary files /dev/null and b/app-engine/shaka-player-demo/posters/1yo.jpg differ
diff --git a/app-engine/shaka-player-demo/posters/2yo.png b/app-engine/shaka-player-demo/posters/2yo.png
new file mode 100644
index 0000000000..f945d6f231
Binary files /dev/null and b/app-engine/shaka-player-demo/posters/2yo.png differ
diff --git a/app-engine/shaka-player-demo/posters/2yo.xcf b/app-engine/shaka-player-demo/posters/2yo.xcf
new file mode 100644
index 0000000000..dcc9de1aae
Binary files /dev/null and b/app-engine/shaka-player-demo/posters/2yo.xcf differ
diff --git a/app-engine/shaka-player-demo/posters/3yo.png b/app-engine/shaka-player-demo/posters/3yo.png
new file mode 100644
index 0000000000..3de5d2c336
Binary files /dev/null and b/app-engine/shaka-player-demo/posters/3yo.png differ
diff --git a/app-engine/shaka-player-demo/posters/3yo.xcf b/app-engine/shaka-player-demo/posters/3yo.xcf
new file mode 100644
index 0000000000..8abedc2cab
Binary files /dev/null and b/app-engine/shaka-player-demo/posters/3yo.xcf differ
diff --git a/app-engine/shaka-player-demo/posters/4yo.png b/app-engine/shaka-player-demo/posters/4yo.png
new file mode 100644
index 0000000000..2c3cb07496
Binary files /dev/null and b/app-engine/shaka-player-demo/posters/4yo.png differ
diff --git a/app-engine/shaka-player-demo/posters/4yo.xcf b/app-engine/shaka-player-demo/posters/4yo.xcf
new file mode 100644
index 0000000000..433594aaf5
Binary files /dev/null and b/app-engine/shaka-player-demo/posters/4yo.xcf differ
diff --git a/app-engine/shaka-player-demo/posters/5yo.png b/app-engine/shaka-player-demo/posters/5yo.png
new file mode 100644
index 0000000000..d2432bc6df
Binary files /dev/null and b/app-engine/shaka-player-demo/posters/5yo.png differ
diff --git a/app-engine/shaka-player-demo/posters/5yo.xcf b/app-engine/shaka-player-demo/posters/5yo.xcf
new file mode 100644
index 0000000000..efe908ff27
Binary files /dev/null and b/app-engine/shaka-player-demo/posters/5yo.xcf differ
diff --git a/app-engine/shaka-player-demo/posters/6yo.png b/app-engine/shaka-player-demo/posters/6yo.png
new file mode 100644
index 0000000000..2971548973
Binary files /dev/null and b/app-engine/shaka-player-demo/posters/6yo.png differ
diff --git a/app-engine/shaka-player-demo/posters/6yo.xcf b/app-engine/shaka-player-demo/posters/6yo.xcf
new file mode 100644
index 0000000000..b1da408b8f
Binary files /dev/null and b/app-engine/shaka-player-demo/posters/6yo.xcf differ
diff --git a/app-engine/shaka-player-demo/posters/7yo.png b/app-engine/shaka-player-demo/posters/7yo.png
new file mode 100644
index 0000000000..7469cb1315
Binary files /dev/null and b/app-engine/shaka-player-demo/posters/7yo.png differ
diff --git a/app-engine/shaka-player-demo/posters/7yo.xcf b/app-engine/shaka-player-demo/posters/7yo.xcf
new file mode 100644
index 0000000000..2526f10da3
Binary files /dev/null and b/app-engine/shaka-player-demo/posters/7yo.xcf differ
diff --git a/app-engine/shaka-player-demo/posters/8yo.png b/app-engine/shaka-player-demo/posters/8yo.png
new file mode 100644
index 0000000000..6de3adb5ed
Binary files /dev/null and b/app-engine/shaka-player-demo/posters/8yo.png differ
diff --git a/app-engine/shaka-player-demo/posters/8yo.xcf b/app-engine/shaka-player-demo/posters/8yo.xcf
new file mode 100644
index 0000000000..d6907d71de
Binary files /dev/null and b/app-engine/shaka-player-demo/posters/8yo.xcf differ
diff --git a/app-engine/shaka-player-demo/posters/9yo.png b/app-engine/shaka-player-demo/posters/9yo.png
new file mode 100644
index 0000000000..c731ad82db
Binary files /dev/null and b/app-engine/shaka-player-demo/posters/9yo.png differ
diff --git a/app-engine/shaka-player-demo/posters/9yo.xcf b/app-engine/shaka-player-demo/posters/9yo.xcf
new file mode 100644
index 0000000000..32e086bb26
Binary files /dev/null and b/app-engine/shaka-player-demo/posters/9yo.xcf differ
diff --git a/app-engine/shaka-player-demo/posters/audioOnly.gif b/app-engine/shaka-player-demo/posters/audioOnly.gif
new file mode 100644
index 0000000000..3ee856a27c
Binary files /dev/null and b/app-engine/shaka-player-demo/posters/audioOnly.gif differ
diff --git a/app-engine/shaka-player-demo/posters/audioRickRoll.gif b/app-engine/shaka-player-demo/posters/audioRickRoll.gif
new file mode 100644
index 0000000000..20b969f9bb
Binary files /dev/null and b/app-engine/shaka-player-demo/posters/audioRickRoll.gif differ
diff --git a/app-engine/shaka-player-demo/posters/colorbars.gif b/app-engine/shaka-player-demo/posters/colorbars.gif
new file mode 100644
index 0000000000..d14b62089a
Binary files /dev/null and b/app-engine/shaka-player-demo/posters/colorbars.gif differ
diff --git a/app-engine/shaka-player-demo/posters/fireworks.jpg b/app-engine/shaka-player-demo/posters/fireworks.jpg
new file mode 100644
index 0000000000..dad18b3994
Binary files /dev/null and b/app-engine/shaka-player-demo/posters/fireworks.jpg differ
diff --git a/app-engine/shaka-player-demo/posters/party-hat.png b/app-engine/shaka-player-demo/posters/party-hat.png
new file mode 100644
index 0000000000..4e0badce42
Binary files /dev/null and b/app-engine/shaka-player-demo/posters/party-hat.png differ
diff --git a/app-engine/shaka-player-demo/posters/sources.txt b/app-engine/shaka-player-demo/posters/sources.txt
new file mode 100644
index 0000000000..b4846e42c7
--- /dev/null
+++ b/app-engine/shaka-player-demo/posters/sources.txt
@@ -0,0 +1,46 @@
+Trek poster image derived from:
+ https://commons.wikimedia.org/wiki/File:Leonard_Nimoy_by_Gage_Skidmore_2.jpg
+Author:
+ https://commons.wikimedia.org/wiki/User:Gage
+License:
+ Creative Commons Share-Alike 3.0
+ http://creativecommons.org/licenses/by-sa/3.0/
+
+Zombie poster image derived from:
+ https://www.flickr.com/photos/ofsmallthings/8055153041
+Author:
+ https://www.flickr.com/photos/ofsmallthings/
+License:
+ Creative Commons Share-Alike 2.0
+ https://creativecommons.org/licenses/by-sa/2.0/
+
+Color bars April Fool's image from:
+ http://kittypizzadope.tumblr.com/post/66751567701
+Author:
+ unknown
+License:
+ unknown
+
+Party hat image from:
+ https://pixabay.com/en/party-hat-celebration-birthday-42329/
+Author:
+ unknown
+License:
+ Creative Commons 0 - Public Domain 1.0
+ https://creativecommons.org/publicdomain/zero/1.0/deed.en
+
+Party streamers image from:
+ https://pixabay.com/en/confetti-streamer-party-carnival-1925258/
+Author:
+ Unknown
+License:
+ Creative Commons 0 - Public Domain 1.0
+ https://creativecommons.org/publicdomain/zero/1.0/deed.en
+
+Fireworks image from:
+ https://pixabay.com/illustrations/fireworks-sylvester-pyrotechnics-2248223/
+Author:
+ https://pixabay.com/users/geralt-9301/
+License:
+ Simplified Pixabay License
+ https://pixabay.com/service/license/
diff --git a/app-engine/shaka-player-demo/posters/standard.png b/app-engine/shaka-player-demo/posters/standard.png
new file mode 100644
index 0000000000..be3300e0eb
Binary files /dev/null and b/app-engine/shaka-player-demo/posters/standard.png differ
diff --git a/app-engine/shaka-player-demo/posters/trek.jpg b/app-engine/shaka-player-demo/posters/trek.jpg
new file mode 100644
index 0000000000..bb75d4d7dd
Binary files /dev/null and b/app-engine/shaka-player-demo/posters/trek.jpg differ
diff --git a/app-engine/shaka-player-demo/posters/valentines.png b/app-engine/shaka-player-demo/posters/valentines.png
new file mode 100644
index 0000000000..9349bdc8c3
Binary files /dev/null and b/app-engine/shaka-player-demo/posters/valentines.png differ
diff --git a/app-engine/shaka-player-demo/posters/zombie.jpg b/app-engine/shaka-player-demo/posters/zombie.jpg
new file mode 100644
index 0000000000..7f5627231c
Binary files /dev/null and b/app-engine/shaka-player-demo/posters/zombie.jpg differ
diff --git a/app-engine/shaka-player-demo/requirements.txt b/app-engine/shaka-player-demo/requirements.txt
new file mode 100644
index 0000000000..21e169e84e
--- /dev/null
+++ b/app-engine/shaka-player-demo/requirements.txt
@@ -0,0 +1,5 @@
+# Shaka Player Demo - Appspot Python Runtime Requirements
+# Copyright 2022 Google LLC
+# SPDX-License-Identifier: Apache-2.0
+
+Flask==2.0.3
diff --git a/app-engine/shaka-player-demo/time.txt b/app-engine/shaka-player-demo/time.txt
new file mode 100644
index 0000000000..5a922560cc
--- /dev/null
+++ b/app-engine/shaka-player-demo/time.txt
@@ -0,0 +1 @@
+Time is an illusion; lunchtime doubly so.
diff --git a/package.json b/package.json
index 590c0e32e5..f8efd259a9 100644
--- a/package.json
+++ b/package.json
@@ -60,6 +60,27 @@
"tippy.js": "^4.3.5",
"which": "^2.0.2"
},
+ "shakaCustom": {
+ "purposeOfDemoDeps": "These are the files from node_modules that need to be deployed with the demo app.",
+ "demoDeps": [
+ "awesomplete/awesomplete.css",
+ "awesomplete/awesomplete.min.js",
+ "dialog-polyfill/dialog-polyfill.css",
+ "dialog-polyfill/dist/dialog-polyfill.js",
+ "eme-encryption-scheme-polyfill/index.js",
+ "es6-promise-polyfill/promise.min.js",
+ "fastestsmallesttextencoderdecoder/EncoderDecoderTogether.min.js",
+ "google-closure-library/closure/goog/base.js",
+ "less/dist/less.js",
+ "material-design-lite/dist/material.indigo-blue.min.css",
+ "material-design-lite/dist/material.min.js",
+ "mux.js/dist/mux.min.js",
+ "popper.js/dist/umd/popper.min.js",
+ "pwacompat/pwacompat.min.js",
+ "tippy.js/umd/index.min.js",
+ "tippy.js/index.css"
+ ]
+ },
"main": "dist/shaka-player.compiled.js",
"repository": {
"type": "git",