From 395ca65b55abe65a57077737ca6a2a2c193be4ff Mon Sep 17 00:00:00 2001 From: Adam Fox Date: Tue, 7 Feb 2023 09:43:33 -0800 Subject: [PATCH] February 2023 Release of the APL 2023.1 compliant APL Viewhost Web For more details on this release refer to CHANGELOG.md To learn about APL see: https://developer.amazon.com/docs/alexa-presentation-language/understand-apl.html --- CHANGELOG.md | 10 + README.md | 10 +- js/apl-html/package.json | 5 +- js/apl-html/src/CommandFactory.ts | 83 ++++++ js/apl-html/src/ComponentFactory.ts | 39 +-- js/apl-html/src/components/Component.ts | 2 +- js/apl-html/src/components/filters/Blend.ts | 56 ++-- js/apl-html/src/components/filters/Blur.ts | 52 ++++ .../src/components/filters/Saturate.ts | 57 ++++ js/apl-html/src/index.ts | 1 + js/apl-html/src/media/IMediaSource.ts | 33 ++- js/apl-html/src/media/IVideoPlayer.ts | 3 + js/apl-html/src/media/MediaEventProcessor.ts | 56 ++-- js/apl-html/src/media/MediaPlayerHandle.ts | 12 +- js/apl-html/src/media/PlaybackManager.ts | 6 +- js/apl-html/src/media/audio/SpeechMarks.ts | 2 +- .../media/video/PlayerNetworkRetryManager.ts | 54 ++++ js/apl-html/src/media/video/VideoPlayer.ts | 253 ++++++++++++++++++ js/apl-html/src/utils/AplVersionUtils.ts | 4 +- js/apl-html/src/utils/TextTrackUtils.ts | 123 +++++++++ js/apl-wasm/package.json | 5 +- js/dts-packer/package.json | 5 +- js/dts-packer/src/index.ts | 2 +- js/package.json | 5 +- js/yarn.lock | 143 ++++++---- package.json | 2 +- scripts/fetch.js | 2 +- wasm/CMakeLists.txt | 1 + wasm/config.cmake | 4 +- wasm/include/wasm/graphicpattern.h | 29 ++ wasm/src/audioplayer.cpp | 2 +- wasm/src/context.cpp | 6 +- wasm/src/embindutils.cpp | 103 ++++--- wasm/src/mediaplayer.cpp | 29 +- 34 files changed, 989 insertions(+), 210 deletions(-) create mode 100644 js/apl-html/src/CommandFactory.ts create mode 100644 js/apl-html/src/components/filters/Blur.ts create mode 100644 js/apl-html/src/components/filters/Saturate.ts create mode 100644 js/apl-html/src/media/video/PlayerNetworkRetryManager.ts create mode 100644 js/apl-html/src/media/video/VideoPlayer.ts create mode 100644 js/apl-html/src/utils/TextTrackUtils.ts create mode 100644 wasm/include/wasm/graphicpattern.h diff --git a/CHANGELOG.md b/CHANGELOG.md index d6dd944..7fa2486 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,15 @@ # Changelog for apl-viewhost-web +## [2023.1] +This release adds support for version 2023.1 of the APL specification. Please also see APL Core Library for changes: [apl-core-library CHANGELOG](https://github.com/alexa/apl-core-library/blob/master/CHANGELOG.md) + +### Added +- SRT support for APL Video textTrack +- Support for new Porter-Duff blend modes + +### Changed +- Bug fixes + ## [2022.2] This release adds support for version 2022.2 of the APL specification. Please also see APL Core Library for changes: [apl-core-library CHANGELOG](https://github.com/alexa/apl-core-library/blob/master/CHANGELOG.md) diff --git a/README.md b/README.md index 2a48cdf..57fe159 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,10 @@ # Alexa Presentation Language (APL) Viewhost Web

- - - - + + + +

## Introduction @@ -16,7 +16,7 @@ platform or framework for which the view host was designed by leveraging the fun ### Prerequisites -* [NodeJS](https://nodejs.org/en/) - version 10.x or higher +* [NodeJS](https://nodejs.org/en/) - version 16.x or higher * [cmake](https://cmake.org/install/) - the easiest way to install on Mac is using `brew install cmake` * [Yarn](https://yarnpkg.com/getting-started/install) diff --git a/js/apl-html/package.json b/js/apl-html/package.json index ef407ac..b2d3296 100644 --- a/js/apl-html/package.json +++ b/js/apl-html/package.json @@ -1,5 +1,5 @@ { - "name": "apl-html", + "name": "@amzn/apl-html", "version": "1.0.0", "license": "SEE LICENSE IN LICENSE.txt", "main": "lib/index.js", @@ -52,5 +52,8 @@ "webpack-cli": "^3.3.12", "webpack-merge": "^4.2.1", "xregexp": "4.2.4" + }, + "npm-pretty-much": { + "legacyPackageNameAlias": "apl-html" } } diff --git a/js/apl-html/src/CommandFactory.ts b/js/apl-html/src/CommandFactory.ts new file mode 100644 index 0000000..fb0543a --- /dev/null +++ b/js/apl-html/src/CommandFactory.ts @@ -0,0 +1,83 @@ +/*! + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import APLRenderer from './APLRenderer'; +import { EventType } from './enums/EventType'; +import { DataSourceFetchRequest } from './events/DataSourceFetchRequest'; +import { ExtensionEvent } from './events/ExtensionEvent'; +import { Finish } from './events/Finish'; +import { Focus } from './events/Focus'; +import { LineHighlight } from './events/LineHighlight'; +import { MediaRequest } from './events/MediaRequest'; +import { OpenUrl } from './events/OpenUrl'; +import { ReInflate} from './events/ReInflate'; +import { RequestLineBounds } from './events/RequestLineBounds'; +import { SendEvent } from './events/SendEvent'; + +/** + * Creates and executes a command + * @param event The core engine event + * @param renderer A reference to the renderer instance + * @internal + */ +export const commandFactory = (event: APL.Event, renderer: APLRenderer) => { + if (factoryMap[event.getType()]) { + return factoryMap[event.getType()](event, renderer); + } + throw new Error(`Cannot create command with type ${event.getType()}`); +}; + +const factoryMap = { + [EventType.kEventTypeSendEvent]: (event: APL.Event, renderer: APLRenderer) => { + const command = new SendEvent(event, renderer); + command.execute(); + return command; + }, + [EventType.kEventTypeRequestLineBounds]: (event: APL.Event, renderer: APLRenderer) => { + const command = new RequestLineBounds(event, renderer); + command.execute(); + return command; + }, + [EventType.kEventTypeLineHighlight]: (event: APL.Event, renderer: APLRenderer) => { + const command = new LineHighlight(event, renderer); + command.execute(); + return command; + }, + [EventType.kEventTypeReinflate]: (event: APL.Event, renderer: APLRenderer) => { + const command = new ReInflate(event, renderer); + command.execute(); + return command; + }, + [EventType.kEventTypeFinish]: (event: APL.Event, renderer: APLRenderer) => { + const command = new Finish(event, renderer); + command.execute(); + return command; + }, + [EventType.kEventTypeFocus]: (event: APL.Event, renderer: APLRenderer) => { + const command = new Focus(event, renderer); + command.execute(); + return command; + }, + [EventType.kEventTypeOpenURL]: (event: APL.Event, renderer: APLRenderer) => { + const command = new OpenUrl(event, renderer); + command.execute(); + return command; + }, + [EventType.kEventTypeDataSourceFetchRequest]: (event: APL.Event, renderer: APLRenderer) => { + const command = new DataSourceFetchRequest(event, renderer); + command.execute(); + return command; + }, + [EventType.kEventTypeExtension]: (event: APL.Event, renderer: APLRenderer) => { + const command = new ExtensionEvent(event, renderer); + command.execute(); + return command; + }, + [EventType.kEventTypeMediaRequest]: (event: APL.Event, renderer: APLRenderer) => { + const command = new MediaRequest(event, renderer); + command.execute(); + return command; + } +}; diff --git a/js/apl-html/src/ComponentFactory.ts b/js/apl-html/src/ComponentFactory.ts index ebab33a..2a625d5 100644 --- a/js/apl-html/src/ComponentFactory.ts +++ b/js/apl-html/src/ComponentFactory.ts @@ -24,8 +24,9 @@ export const componentFactory = (renderer: APLRenderer, component: APL.Component parent?: Component, ensureLayout: boolean = false, insertAt: number = -1 ): Component => { const id = component.getUniqueId(); + let comp; if (renderer.componentMap[id]) { - const comp = renderer.componentMap[id]; + comp = renderer.componentMap[id]; comp.parent = parent; if (ensureLayout && comp instanceof Text) { comp.setDimensions(); @@ -40,25 +41,29 @@ export const componentFactory = (renderer: APLRenderer, component: APL.Component } return comp; } else if (factoryMap[component.getType()]) { - const item = factoryMap[component.getType()](renderer, component, parent); - if (ensureLayout) { - if (item instanceof Text) { - item.init(); - } - item.component.ensureLayout(); - item.init(); - if (parent) { - if (insertAt >= 0 && parent.container.children.length > 0 && - insertAt < parent.container.children.length) { - parent.container.insertBefore(item.container, parent.container.children.item(insertAt)); - } else { - parent.container.appendChild(item.container); - } + comp = factoryMap[component.getType()](renderer, component, parent); + } else { + // Any unknown component is effectively container + comp = new Container(renderer, component, componentFactory, parent); + } + + if (ensureLayout) { + if (comp instanceof Text) { + comp.init(); + } + comp.component.ensureLayout(); + comp.init(); + if (parent) { + if (insertAt >= 0 && parent.container.children.length > 0 && + insertAt < parent.container.children.length) { + parent.container.insertBefore(comp.container, parent.container.children.item(insertAt)); + } else { + parent.container.appendChild(comp.container); } } - return item; } - throw new Error(`Cannot create component with type ${component.getType()}`); + + return comp; }; // tslint:disable:max-line-length diff --git a/js/apl-html/src/components/Component.ts b/js/apl-html/src/components/Component.ts index 0e2ad37..b0d1fb2 100644 --- a/js/apl-html/src/components/Component.ts +++ b/js/apl-html/src/components/Component.ts @@ -741,7 +741,7 @@ export abstract class Component extends EventEmitt const isLegacyComponentType: boolean = LEGACY_CLIPPING_COMPONENTS_SET.has(componentType); const isLegacyAplVersion: boolean = this.renderer && this.renderer.getLegacyClippingEnabled(); - if (isLegacyComponentType || isParentLegacy || !isLegacyAplVersion) { + if (!this.parent || isLegacyComponentType || isParentLegacy || !isLegacyAplVersion) { this.enableClipping(); } } diff --git a/js/apl-html/src/components/filters/Blend.ts b/js/apl-html/src/components/filters/Blend.ts index 1891e08..394c8ef 100644 --- a/js/apl-html/src/components/filters/Blend.ts +++ b/js/apl-html/src/components/filters/Blend.ts @@ -26,11 +26,21 @@ export interface IBlend extends IBaseFilter { * https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feBlend */ +enum BlendType { + Blend = 'feBlend', + Composite = 'feComposite' +} + export function getBlendFilter(filter: Filter, imageSrcArray: string[]): IImageFilterElement | undefined { const blendId: string = uuidv4().toString(); let filterImageArray: SVGFEImageElement[] = []; - const blend: SVGElement = document.createElementNS(SVG_NS, 'feBlend'); - blend.setAttributeNS('', 'mode', getBlendMode((filter as IBlend).mode)); + const [elementType, operator] = getBlendMode((filter as IBlend).mode); + const blend: SVGElement = document.createElementNS(SVG_NS, elementType); + if (elementType === BlendType.Composite) { + blend.setAttributeNS('', 'operator', operator); + } else { + blend.setAttributeNS('', 'mode', operator); + } blend.setAttributeNS('', 'result', blendId); /* @@ -78,41 +88,47 @@ export function getBlendFilter(filter: Filter, imageSrcArray: string[]): IImageF * Return Blend Mode * https://codepen.io/yoksel/pen/BiExv */ -export function getBlendMode(mode: BlendMode): string { +function getBlendMode(mode: BlendMode): [BlendType, string] { switch (mode) { case BlendMode.kBlendModeNormal: - return 'normal'; + return [BlendType.Blend, 'normal']; case BlendMode.kBlendModeMultiply: - return 'multiply'; + return [BlendType.Blend, 'multiply']; case BlendMode.kBlendModeScreen: - return 'screen'; + return [BlendType.Blend, 'screen']; case BlendMode.kBlendModeOverlay: - return 'overlay'; + return [BlendType.Blend, 'overlay']; case BlendMode.kBlendModeDarken: - return 'darken'; + return [BlendType.Blend, 'darken']; case BlendMode.kBlendModeLighten: - return 'lighten'; + return [BlendType.Blend, 'lighten']; case BlendMode.kBlendModeColorDodge: - return 'color-dodge'; + return [BlendType.Blend, 'color-dodge']; case BlendMode.kBlendModeColorBurn: - return 'color-burn'; + return [BlendType.Blend, 'color-burn']; case BlendMode.kBlendModeHardLight: - return 'hard-light'; + return [BlendType.Blend, 'hard-light']; case BlendMode.kBlendModeSoftLight: - return 'soft-light'; + return [BlendType.Blend, 'soft-light']; case BlendMode.kBlendModeDifference: - return 'difference'; + return [BlendType.Blend, 'difference']; case BlendMode.kBlendModeExclusion: - return 'exclusion'; + return [BlendType.Blend, 'exclusion']; case BlendMode.kBlendModeHue: - return 'hue'; + return [BlendType.Blend, 'hue']; case BlendMode.kBlendModeSaturation: - return 'saturation'; + return [BlendType.Blend, 'saturation']; case BlendMode.kBlendModeColor: - return 'color'; + return [BlendType.Blend, 'color']; case BlendMode.kBlendModeLuminosity: - return 'luminosity'; + return [BlendType.Blend, 'luminosity']; + case BlendMode.kBlendModeSourceIn: + return [BlendType.Composite, 'in']; + case BlendMode.kBlendModeSourceAtop: + return [BlendType.Composite, 'atop']; + case BlendMode.kBlendModeSourceOut: + return [BlendType.Composite, 'out']; default: - return 'normal'; + return [BlendType.Blend, 'normal']; } } diff --git a/js/apl-html/src/components/filters/Blur.ts b/js/apl-html/src/components/filters/Blur.ts new file mode 100644 index 0000000..b89b837 --- /dev/null +++ b/js/apl-html/src/components/filters/Blur.ts @@ -0,0 +1,52 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +'use strict'; + +import { Filter, generateSVGFeImage, isIndexOutOfBound } from '../../utils/FilterUtils'; +import { SVG_NS, uuidv4 } from '../Component'; +import { BITMAP_IMAGE_REGEX_CHECK, IBaseFilter, IImageFilterElement } from './ImageFilter'; + +/** + * @ignore + */ +export interface IBlur extends IBaseFilter { + radius: number; + source: number; +} + +/* + * Apply a Gaussian blur with a specified radius. The new image is appended to the end of the array. + * Specs: https://developer.amazon.com/en-US/docs/alexa/alexa-presentation-language/apl-filters.html#blur + * Utilize svg filter + * https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feGaussianBlur + */ +export function getBlurFilter(filter: Filter, imageSrcArray: string[]): IImageFilterElement | undefined { + const blurId: string = uuidv4().toString(); + let filterImageArray: SVGFEImageElement[] = []; + const blur: SVGElement = document.createElementNS(SVG_NS, 'feGaussianBlur'); + blur.setAttributeNS('', 'stdDeviation', (filter as IBlur).radius.toString()); + blur.setAttributeNS('', 'result', blurId); + /* + * All filters that operate on a single image have a default image source property of -1; + * that is, by default they take as input the last image in the image array. + */ + let index: number = (filter as IBlur).source; + + // Negative case : index outside source array bounds. return undefined + if (isIndexOutOfBound(index, imageSrcArray.length)) { + return undefined; + } + if (index < 0) { + index += imageSrcArray.length; + } + const imageId: string = imageSrcArray[index]; + if (imageId.match(BITMAP_IMAGE_REGEX_CHECK)) { + filterImageArray = generateSVGFeImage(imageId, blur); + } else { + blur.setAttributeNS('', 'in', imageId); + } + return { filterId: blurId, filterElement: blur, filterImageArray }; +} diff --git a/js/apl-html/src/components/filters/Saturate.ts b/js/apl-html/src/components/filters/Saturate.ts new file mode 100644 index 0000000..98733f6 --- /dev/null +++ b/js/apl-html/src/components/filters/Saturate.ts @@ -0,0 +1,57 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +'use strict'; + +import { Filter, generateSVGFeImage, isIndexOutOfBound } from '../../utils/FilterUtils'; +import { SVG_NS, uuidv4 } from '../Component'; +import { BITMAP_IMAGE_REGEX_CHECK, IBaseFilter, IImageFilterElement } from './ImageFilter'; + +/** + * @ignore + */ +export interface ISaturate extends IBaseFilter { + amount: number; + source?: number; +} + +/* + * Change the color saturation of the image and appends the new image to the end of the array. + * Specs: https://developer.amazon.com/en-US/docs/alexa/alexa-presentation-language/apl-filters.html#saturate + * Utilize svg filter + * https://developer.mozilla.org/en-US/docs/Web/SVG/Element/feColorMatrix + */ + +export function getSaturateFilter(filter: Filter, imageSrcArray: string[]): IImageFilterElement | undefined { + const saturateId: string = uuidv4().toString(); + let filterImageArray: SVGFEImageElement[] = []; + const saturate: SVGElement = document.createElementNS(SVG_NS, 'feColorMatrix'); + saturate.setAttribute('type', 'saturate'); + saturate.setAttribute('values', (filter as ISaturate).amount.toString()); + saturate.setAttributeNS('', 'result', saturateId); + + /* + * An amount of 0% completed unsaturates the image. + * An amount of 100% leaves the image the same. + * Values greater than 100% produce super-saturation. + * The source image must be a bitmap + */ + let index: number = (filter as ISaturate).source; + + // Negative case : index outside source array bounds. return undefined + if (isIndexOutOfBound(index, imageSrcArray.length)) { + return undefined; + } + if (index < 0) { + index += imageSrcArray.length; + } + const imageId: string = imageSrcArray[index]; + if (imageId.match(BITMAP_IMAGE_REGEX_CHECK)) { + filterImageArray = generateSVGFeImage(imageId, saturate); + } else { + saturate.setAttributeNS('', 'in', imageId); + } + return { filterId : saturateId, filterElement : saturate, filterImageArray }; +} diff --git a/js/apl-html/src/index.ts b/js/apl-html/src/index.ts index ace852b..52c15bc 100644 --- a/js/apl-html/src/index.ts +++ b/js/apl-html/src/index.ts @@ -100,6 +100,7 @@ export * from './utils/EventUtils'; export * from './utils/FilterUtils'; export * from './utils/FontUtils'; export * from './utils/SoftRandom'; +export * from './utils/TextTrackUtils'; export * from './utils/LocaleMethods'; export * from './logging/ILogger'; export * from './logging/LoggerFactory'; diff --git a/js/apl-html/src/media/IMediaSource.ts b/js/apl-html/src/media/IMediaSource.ts index 1d27c24..1e2a760 100644 --- a/js/apl-html/src/media/IMediaSource.ts +++ b/js/apl-html/src/media/IMediaSource.ts @@ -4,7 +4,7 @@ */ /** - * MediaTrack defines the source and playback parameters + * IMediaSource defines the source and playback parameters * @ignore */ export interface IMediaSource { @@ -13,11 +13,6 @@ export interface IMediaSource { */ url: string; - /** - * Optional description of this source - */ - description: string; - /** * Duration of the track in milliseconds */ @@ -32,4 +27,30 @@ export interface IMediaSource { * Milliseconds from the start of the track to play from */ offset: number; + + /** + * Text tracks of the media + */ + textTracks: Array; } + +/** + * ITextTrackSource defines the text track attached to a media source + * @ignore + */ +export interface ITextTrackSource { + /** + * The kind of the text track + */ + kind: string; + + /** + * The actual URL to load the text track + */ + url: string; + + /** + * Optional description of this source + */ + description: string; +} \ No newline at end of file diff --git a/js/apl-html/src/media/IVideoPlayer.ts b/js/apl-html/src/media/IVideoPlayer.ts index cdbe92d..17a6060 100644 --- a/js/apl-html/src/media/IVideoPlayer.ts +++ b/js/apl-html/src/media/IVideoPlayer.ts @@ -2,6 +2,7 @@ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. */ +import { ITextTrackSource } from './IMediaSource'; import {IPlayer} from './IPlayer'; export interface IVideoPlayer extends IPlayer { @@ -28,4 +29,6 @@ export interface IVideoPlayer extends IPlayer { reset(): void; destroy(): void; + + loadTextTracks(textTracks: ITextTrackSource[]): void; } diff --git a/js/apl-html/src/media/MediaEventProcessor.ts b/js/apl-html/src/media/MediaEventProcessor.ts index e76fdde..b207ba2 100644 --- a/js/apl-html/src/media/MediaEventProcessor.ts +++ b/js/apl-html/src/media/MediaEventProcessor.ts @@ -12,6 +12,7 @@ import { IMediaEventListener } from './IMediaEventListener'; import { IMediaPlayerHandle } from './IMediaPlayerHandle'; import { IVideoPlayer } from './IVideoPlayer'; import { MediaErrorCode } from './MediaErrorCode'; +import { MediaPlayerHandle } from './MediaPlayerHandle'; import { MediaState } from './MediaState'; import { IMediaResource, PlaybackManager } from './PlaybackManager'; import { PlaybackState } from './Resource'; @@ -36,7 +37,6 @@ export function createMediaEventProcessor(mediaEventProcessorArgs: MediaEventPro // Private Variables let videoState: PlaybackState = PlaybackState.IDLE; - const endEventPromiseListeners: PromiseCallback[] = []; // Private Functions async function ensureLoaded(fromEvent: boolean, isSettingSource: boolean = false) { @@ -44,6 +44,7 @@ export function createMediaEventProcessor(mediaEventProcessorArgs: MediaEventPro if (!currentMediaResource.loaded) { await this.player.load(currentMediaResource.id, currentMediaResource.url); currentMediaResource.loaded = true; + this.player.loadTextTracks(currentMediaResource.textTracks); } else { this.updateMediaState(fromEvent, isSettingSource); } @@ -58,14 +59,14 @@ export function createMediaEventProcessor(mediaEventProcessorArgs: MediaEventPro if (!this.loaded) { logger.info('First loaded, refresh video'); this.setTrackIndex({trackIndex: 0, fromEvent: false}); - this.rewind(); + this.rewind({fromEvent: false}); this.loaded = true; } if (this.shouldStartPlayAfterPlayerInit) { logger.info('Player ready, starting playback'); this.play({ waitForFinish: false, - fromEvent: true, + fromEvent: false, isSettingSource: false }); } @@ -117,12 +118,6 @@ export function createMediaEventProcessor(mediaEventProcessorArgs: MediaEventPro this.currentMediaState.paused = true; mediaPlayerEventType = MediaPlayerEventType.kMediaPlayerEventEnd; } - endEventPromiseListeners.forEach((resolvePromise) => { - try { - resolvePromise(); - } catch (e) { - } - }); break; case PlaybackState.ERROR: logger.error('Playback error.'); @@ -182,12 +177,6 @@ export function createMediaEventProcessor(mediaEventProcessorArgs: MediaEventPro } } ); - - if (waitForFinish) { - await new Promise((resolve) => { - endEventPromiseListeners.push(resolve); - }); - } }, async pause(): Promise { if (videoState === PlaybackState.PLAYING || videoState === PlaybackState.BUFFERING) { @@ -231,8 +220,10 @@ export function createMediaEventProcessor(mediaEventProcessorArgs: MediaEventPro this.updateMediaState(fromEvent); }, - async rewind(): Promise { - await this.pause(); + async rewind({ fromEvent }): Promise { + if (fromEvent) { + await this.pause(); + } const currentMediaResource = this.playbackManager.getCurrent(); this.player.setCurrentTimeInSeconds( toSecondsFromMilliseconds(currentMediaResource.offset) @@ -240,16 +231,19 @@ export function createMediaEventProcessor(mediaEventProcessorArgs: MediaEventPro }, async previous({ fromEvent }): Promise { if (!this.playbackManager.hasPrevious()) { - await this.delegate.rewind(); + await this.delegate.rewind(fromEvent); } else { - await this.pause(); + if (fromEvent) { + await this.pause(); + } this.playbackManager.previous(); } await ensureLoaded.call(this, fromEvent); }, async next({ fromEvent }): Promise { - await this.pause(); - + if (fromEvent) { + await this.pause(); + } if (!this.playbackManager.hasNext()) { this.player.setCurrentTimeInSeconds(this.player.getDurationInSeconds() - 0.001); } else { @@ -258,7 +252,9 @@ export function createMediaEventProcessor(mediaEventProcessorArgs: MediaEventPro } }, async setTrackIndex({ trackIndex, fromEvent }): Promise { - await this.pause(); + if (fromEvent) { + await this.pause(); + } this.playbackManager.setCurrent(trackIndex); this.updateMediaState(fromEvent, true); await ensureLoaded.call(this, fromEvent); @@ -268,14 +264,16 @@ export function createMediaEventProcessor(mediaEventProcessorArgs: MediaEventPro this.audioTrack = audioTrack; }, setMuted({ muted, fromEvent }) { - this.muted = muted; + if (this.audioTrack !== AudioTrack.kAudioTrackNone) { + this.muted = muted; - if (this.muted) { - this.player.mute(); - } else { - this.player.unmute(); + if (this.muted) { + this.player.mute(); + } else { + this.player.unmute(); + } + this.updateMediaState(fromEvent); } - this.updateMediaState(fromEvent); }, async setTrackList({ trackArray }): Promise { // Configure Player and Playback @@ -411,7 +409,7 @@ export function createMediaEventProcessor(mediaEventProcessorArgs: MediaEventPro } }); - return Object.setPrototypeOf(mediaEventProcessor, mediaPlayerHandle); + return Object.setPrototypeOf(mediaEventProcessor, mediaPlayerHandle) as MediaPlayerHandle; } function toSecondsFromMilliseconds(milliseconds: number) { diff --git a/js/apl-html/src/media/MediaPlayerHandle.ts b/js/apl-html/src/media/MediaPlayerHandle.ts index a10548b..c5c77d5 100644 --- a/js/apl-html/src/media/MediaPlayerHandle.ts +++ b/js/apl-html/src/media/MediaPlayerHandle.ts @@ -128,17 +128,17 @@ export class MediaPlayerHandle implements IMediaPlayerHandle, IMediaEventListene } public onEvent(event: PlaybackState): void { - this.eventProcessor.onEvent({ - event, - fromEvent: false, - isSettingSource: false, - aplMediaPlayer: this.mediaPlayer - }); if (this.videoComponent) { this.videoComponent.onEvent(event); } else { this.lastPlaybackState = event; } + this.eventSequencer.enqueueForProcessing(VideoInterface.ON_EVENT, { + event, + fromEvent: false, + isSettingSource: false, + aplMediaPlayer: this.mediaPlayer + }); } public onPlayerReady(): void { diff --git a/js/apl-html/src/media/PlaybackManager.ts b/js/apl-html/src/media/PlaybackManager.ts index 4648c61..1bc87a8 100644 --- a/js/apl-html/src/media/PlaybackManager.ts +++ b/js/apl-html/src/media/PlaybackManager.ts @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ const uuidv4 = require('uuid/v4'); -import { IMediaSource } from './IMediaSource'; +import { IMediaSource, ITextTrackSource } from './IMediaSource'; /** * Media resource state @@ -18,6 +18,7 @@ export interface IMediaResource { id: string; loaded: boolean; duration: number; + textTracks: ITextTrackSource[]; } /** @@ -117,7 +118,8 @@ export class PlaybackManager { trackIndex : index, url : track.url, loaded : false, - duration : track.duration + duration : track.duration, + textTracks: track.textTracks }; this.resources.set(index, mediaResource); diff --git a/js/apl-html/src/media/audio/SpeechMarks.ts b/js/apl-html/src/media/audio/SpeechMarks.ts index c0c3717..5839534 100644 --- a/js/apl-html/src/media/audio/SpeechMarks.ts +++ b/js/apl-html/src/media/audio/SpeechMarks.ts @@ -9,7 +9,7 @@ * The type of speech mark * @ignore */ -export type SpeechMarkType = 'visime' | 'word' | 'sentence' | 'ssml'; +export type SpeechMarkType = 'viseme' | 'word' | 'sentence' | 'ssml'; /** * Base type diff --git a/js/apl-html/src/media/video/PlayerNetworkRetryManager.ts b/js/apl-html/src/media/video/PlayerNetworkRetryManager.ts new file mode 100644 index 0000000..41fcc4c --- /dev/null +++ b/js/apl-html/src/media/video/PlayerNetworkRetryManager.ts @@ -0,0 +1,54 @@ +/*! + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +const hls = require('hls.js/dist/hls.light.min.js'); + +export interface PlayerNetworkRetryManagerArgs { + player: HTMLVideoElement | any; + numberOfRetries?: number; + errorCallback?: () => void; +} + +export interface PlayerNetworkRetryManager { + fail: () => void; + shouldRetry: () => boolean; + retry: (url: string, errorDetails: string) => void; +} + +export function createPlayerNetworkRetryManager(args: PlayerNetworkRetryManagerArgs): PlayerNetworkRetryManager { + const defaultArgs = { + numberOfRetries: 3, + errorCallback: () => {} + }; + + args = Object.assign(defaultArgs, args); + + const { + player, + errorCallback + } = args; + + let remainingTries = args.numberOfRetries; + + return { + fail(): void { + errorCallback(); + }, + retry(url, errorDetails): void { + remainingTries -= 1; + + if (errorDetails === hls.ErrorDetails.MANIFEST_LOAD_ERROR || + errorDetails === hls.ErrorDetails.MANIFEST_LOAD_TIMEOUT || + errorDetails === hls.ErrorDetails.MANIFEST_PARSING_ERROR) { + player.loadSource(url); + } else { + player.startLoad(); + } + }, + shouldRetry(): boolean { + return remainingTries > 0; + } + }; +} diff --git a/js/apl-html/src/media/video/VideoPlayer.ts b/js/apl-html/src/media/video/VideoPlayer.ts new file mode 100644 index 0000000..9044586 --- /dev/null +++ b/js/apl-html/src/media/video/VideoPlayer.ts @@ -0,0 +1,253 @@ +/** + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import { clearAllTextTracks, loadDynamicTextTrack, TextTrackKind } from '../../utils/TextTrackUtils'; +import { IMediaEventListener } from '../IMediaEventListener'; +import { ITextTrackSource } from '../IMediaSource'; +import { IVideoPlayer } from '../IVideoPlayer'; +import { PlaybackState } from '../Resource'; +import { EmitBehavior, IPlaybackStateHandler, PlaybackStateHandler } from './PlaybackStateHandler'; + +export interface PlayerInitializationArgs { + player: HTMLVideoElement; + parent: HTMLElement; + scale: 'contain' | 'cover'; +} + +export enum PlaybackFailure { + UNINITIALIZED = 'UNINITIALIZED', + GENERIC = 'GENERIC' +} + +export function createVideoPlayer(eventListener: IMediaEventListener): IVideoPlayer { + // Private Functions + function initializePlayer(args: PlayerInitializationArgs) { + const { + player, + parent, + scale + } = args; + parent.appendChild(player); + Object.assign(player.style, { + 'height': '100%', + 'object-fit': scale, + 'width': '100%' + }); + } + + function positionRelativeToCurrentTime(time: number): Comparison { + if (time > this.player.currentTime) { + return Comparison.GT; + } + if (time < this.player.currentTime) { + return Comparison.LT; + } + return Comparison.EQ; + } + + function updateCurrentTimeTo(time: number): void { + this.player.currentTime = time / 1000; + } + + // Video LifeCycle Callbacks + function attachOnPlayCallback() { + function onPlayCallback(): void { + this.playbackStateHandler.transitionToState(PlaybackState.PLAYING); + } + + this.player.onplay = onPlayCallback.bind(this); + } + + function attachOnPlayingCallback() { + function onPlayingCallback(): void { + this.playbackStateHandler.transitionToState(PlaybackState.PLAYING); + } + + this.player.onplaying = onPlayingCallback.bind(this); + } + + function attachOnEndedCallback() { + function onEndedCallback(): void { + this.playbackStateHandler.transitionToState(PlaybackState.ENDED); + } + + this.player.onended = onEndedCallback.bind(this); + } + + function attachOnPauseCallback() { + function onPauseCallback() { + this.playbackStateHandler.transitionToState(PlaybackState.PAUSED, EmitBehavior.AlwaysEmit); + } + + this.player.onpause = onPauseCallback.bind(this); + } + + function attachOnErrorCallback() { + function onErrorCallback() { + this.playbackStateHandler.transitionToState(PlaybackState.ERROR); + } + this.player.onerror = onErrorCallback.bind(this); + } + + function attachOnLoadedDataCallback() { + function onLoadedDataCallback() { + this.playbackStateHandler.transitionToState(PlaybackState.LOADED); + } + + this.player.onloadeddata = onLoadedDataCallback.bind(this); + } + + function attachOnTimeUpdateCallback() { + function onTimeUpdateCallback() { + const currentPlaybackState = this.playbackStateHandler.currentPlaybackState; + if (currentPlaybackState === PlaybackState.PLAYING) { + this.playbackStateHandler.transitionToState(currentPlaybackState, EmitBehavior.AlwaysEmit); + } + } + + this.player.ontimeupdate = onTimeUpdateCallback.bind(this); + } + + // Public Interface + const videoPlayer: IVideoPlayer = { + // IVideoPlayer Interface + init(): void { + this.playbackStateHandler.transitionToState(PlaybackState.IDLE); + attachOnPlayCallback.call(this); + attachOnPlayingCallback.call(this); + attachOnEndedCallback.call(this); + attachOnPauseCallback.call(this); + attachOnErrorCallback.call(this); + attachOnLoadedDataCallback.call(this); + attachOnTimeUpdateCallback.call(this); + + this.playerIsInitialized = false; + this.shouldStartPlayAfterInit = false; + }, + configure(parent: HTMLElement, scale: 'contain' | 'cover'): void { + initializePlayer({ + player: this.player, + parent, + scale + }); + this.playerIsInitialized = true; + this.eventListener.onPlayerReady(); + }, + applyCssShadow(shadowParams: string): void { + this.player.style.boxShadow = shadowParams; + }, + setCurrentTimeInSeconds(offsetInSeconds: number): void { + this.player.currentTime = offsetInSeconds; + }, + getCurrentPlaybackPositionInSeconds(): number { + return this.player.currentTime; + }, + getDurationInSeconds(): number { + return this.player.duration; + }, + mute(): void { + this.player.muted = true; + }, + unmute(): void { + this.player.muted = false; + }, + // IPlayer Interface + load(id: string, url: string): Promise { + attachOnLoadedDataCallback.call(this); + this.player.id = id; + this.playbackStateHandler.transitionToState(PlaybackState.IDLE); + this.player.src = url; + this.player.load(); + + return Promise.resolve(undefined); + }, + loadTextTracks(textTracks: ITextTrackSource[]) { + clearAllTextTracks(this.player); + if (textTracks && textTracks.length !== 0) { + loadDynamicTextTrack(this.player, textTracks[0].url, textTracks[0].kind as TextTrackKind); + } + }, + play(id: string, url: string, offset: number): Promise { + if (!this.playerIsInitialized) { + return Promise.reject(PlaybackFailure.UNINITIALIZED); + } + const playbackIsNotPaused = !this.playbackStateHandler.isState(PlaybackState.PAUSED); + const offsetIsAhead = positionRelativeToCurrentTime.call(this, offset) === Comparison.GT; + if (playbackIsNotPaused && offsetIsAhead) { + updateCurrentTimeTo.call(this, offset); + } + return new Promise((resolve, reject) => { + this.player.play() + .then(resolve) + .catch(reject); + }); + }, + pause() { + this.playbackStateHandler.transitionToState(PlaybackState.PAUSED); + return this.player.pause(); + }, + end() { + this.playbackStateHandler.transitionToState(PlaybackState.ENDED); + return this.player.pause(); + }, + setVolume(volume: number): void { + this.player.volume = volume; + }, + flush(): void { + this.pause(); + }, + getMediaId(): string { + return this.player.id; + }, + getMediaState(): PlaybackState { + return this.playbackStateHandler.currentPlaybackState; + }, + reset() { + // Pause any existing playback + this.player.pause(); + // Empty out the source + this.player.removeAttribute('src'); + // Reload the player to refresh its state + this.player.load(); + }, + destroy() { + this.reset(); + // Remove from DOM + this.player.remove(); + } + }; + + const videoElement = document.createElement('video'); + const playbackStateHandler = PlaybackStateHandler({ + eventListener, + initialPlaybackState: PlaybackState.IDLE + }); + + // Object Properties + return Object.defineProperties(videoPlayer, { + player: { + value: videoElement as HTMLVideoElement, + writable: false, + configurable: false + }, + playbackStateHandler: { + value: playbackStateHandler as IPlaybackStateHandler, + writable: false, + configurable: false + }, + eventListener: { + value: eventListener as IMediaEventListener, + writable: false, + configurable: false + } + }); +} + +// Helper Enums +enum Comparison { + LT = 'LT', + GT = 'GT', + EQ = 'EQ' +} diff --git a/js/apl-html/src/utils/AplVersionUtils.ts b/js/apl-html/src/utils/AplVersionUtils.ts index efbb5a3..93a91e8 100644 --- a/js/apl-html/src/utils/AplVersionUtils.ts +++ b/js/apl-html/src/utils/AplVersionUtils.ts @@ -16,6 +16,7 @@ export const APL_1_8 = 8; export const APL_1_9 = 9; export const APL_2022_1 = 10; export const APL_2022_2 = 11; +export const APL_2023_1 = 12; export const APL_LATEST = Number.MAX_VALUE; export interface AplVersionUtils { @@ -36,7 +37,8 @@ export function createAplVersionUtils(): AplVersionUtils { ['1.8', APL_1_8], ['1.9', APL_1_9], ['2022.1', APL_2022_1], - ['2022.2', APL_2022_2] + ['2022.2', APL_2022_2], + ['2023.1', APL_2023_1] ]); return { diff --git a/js/apl-html/src/utils/TextTrackUtils.ts b/js/apl-html/src/utils/TextTrackUtils.ts new file mode 100644 index 0000000..3125c79 --- /dev/null +++ b/js/apl-html/src/utils/TextTrackUtils.ts @@ -0,0 +1,123 @@ +/** + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +// We don't want to block the main thread too long. So subtitle format conversion we do by chunks +// This number is small enough that we don't block the main thread too long to miss a frame +// This number is large enough that we don't get too many segments +const DEFAULT_CHUNK_SZIE = 50; + +declare var VTTCue: { + prototype: TextTrackCue; + new(startTime: number, endTime: number, text: string): TextTrackCue; +}; + +export type TextTrackKind = 'captions' | 'chapters' | 'descriptions' | 'metadata' | 'subtitles'; + +export function clearAllTextTracks(video: HTMLVideoElement) { + const tracks = video.querySelectorAll('track'); + for (let i = 0; i < tracks.length; i++) { + video.textTracks[i].mode = 'disabled'; + video.removeChild(tracks[i]); + } +} + +export function loadDynamicTextTrack(video: HTMLVideoElement, url: string, kind: TextTrackKind, + chunkSize: number = DEFAULT_CHUNK_SZIE) { + const track = document.createElement('track'); + track.kind = kind; + video.appendChild(track); + const textTrack = video.textTracks[0]; + + fetch(url, {mode: 'cors'}) + .then((response) => response.text()) + .then((data) => { + textTrack.mode = 'showing'; + + if (getType(data) === 'vtt') { + // HTML text track component doesn't like cors, so we need to create a blob inside the memory + const blob = new Blob([data], { + type: 'text/plain' + }); + track.src = URL.createObjectURL(blob); + } else { + setTimeout(() => { + addDynamicSRTTrack(data, 0, textTrack, chunkSize); + }, 0); + } + }); +} + +function getType(data: string): string { + // VTT has text WEBVTT as the first line + if (data.split('\n', 1)[0].toLowerCase() === 'webvtt') { + // We just ignore WebVTT for now as other viewhost doesn't support it. + return 'unsupported'; + } + + // SRT has no indicator in its file, so we try to match the timestamp line in the first 100 chars + const findLineNumber = /\d+(?:\r\n|\r|\n)(?=(?:\d\d:\d\d:\d\d,\d\d\d)\s-->\s(?:\d\d:\d\d:\d\d,\d\d\d))/g; + if (data.substring(0, 100).match(findLineNumber)) { + return 'srt'; + } + + return 'unsupported'; +} + +// SRT to VTT + +function toTime(timeString: string) { + const t = timeString.match(/(\d+):(\d+):(\d+)(?:,(\d+))/); + // the same regex is used to find the string, so 't' won't be null in any case + const time = Number(t![1]) * 3600 + Number(t![2]) * 60 + Number(t![3]) + Number(t![4]) / 1000; + return time; +} + +function toVTTCue(srtCue: string) { + // match timestamp and the rest of text + // webVtt doesn't support pixel based positioning anyway. So we just ingore the X1,X2,Y1,Y2 in the timestamp line + const matchTimestamp = /(\d\d:\d\d:\d\d,\d\d\d)/g; + const matches = srtCue.match(matchTimestamp); + if (!matches) { + return null; + } + const start = matches[0]; + const end = matches[1]; + let text = srtCue.slice(srtCue.indexOf('\n')).trim(); + // remove the ASS tags. + text = text.replace(/\{\\.*?\}/g, ''); + + const vtt = new VTTCue(toTime(start), toTime(end), text); + return vtt; +} + +function addDynamicSRTTrack(srt: string, cursor: number, textTrack: TextTrack, chunkSize: number) { + if (textTrack.mode !== 'showing') { + return; + } + // find digits followed by a single line break and timestamps + const findLineNumber = /\d+(?:\r\n|\r|\n)(?=(?:\d\d:\d\d:\d\d,\d\d\d)\s-->\s(?:\d\d:\d\d:\d\d,\d\d\d))/g; + const srtSeg = srt.substring(cursor); + + // we don't want to block the main thread too long. So only process chunkSize lines once + const srtArray = srtSeg.split(findLineNumber, chunkSize); + + // remember the parser position for the next iteration + const endSeg = cursor + srtSeg.indexOf(srtArray[srtArray.length - 1]) + srtArray[srtArray.length - 1].length; + if (endSeg === cursor) { + return; + } + + // parser one line of subtitle and convert to vtt + srtArray.forEach((srtCue) => { + const vtt = toVTTCue(srtCue); + if (vtt) { + textTrack.addCue(vtt); + } + }); + + setTimeout(() => { + addDynamicSRTTrack(srt, endSeg, textTrack, chunkSize); + }, 0); +} diff --git a/js/apl-wasm/package.json b/js/apl-wasm/package.json index a838479..92b606c 100644 --- a/js/apl-wasm/package.json +++ b/js/apl-wasm/package.json @@ -1,5 +1,5 @@ { - "name": "apl-wasm", + "name": "@amzn/apl-wasm", "version": "1.0.0", "private": true, "license": "SEE LICENSE IN LICENSE.txt", @@ -58,5 +58,8 @@ "webpack-bundle-analyzer": "^4.6.1", "webpack-cli": "^3.3.12", "webpack-merge": "^4.2.1" + }, + "npm-pretty-much": { + "legacyPackageNameAlias": "apl-wasm" } } diff --git a/js/dts-packer/package.json b/js/dts-packer/package.json index 2e90bca..c8215ff 100644 --- a/js/dts-packer/package.json +++ b/js/dts-packer/package.json @@ -1,5 +1,5 @@ { - "name": "dts-packer", + "name": "@amzn/dts-packer", "version": "1.0.0", "license": "SEE LICENSE IN LICENSE.txt", "main": "lib/index.js", @@ -20,5 +20,8 @@ "webpack": "^4.26.1", "webpack-cli": "^3.3.12", "webpack-merge": "^4.2.1" + }, + "npm-pretty-much": { + "legacyPackageNameAlias": "dts-packer" } } diff --git a/js/dts-packer/src/index.ts b/js/dts-packer/src/index.ts index 431d6cf..370531d 100644 --- a/js/dts-packer/src/index.ts +++ b/js/dts-packer/src/index.ts @@ -221,7 +221,7 @@ export default class DtsPackerPlugin { compiler.plugin('done', () => { const name = path.basename(compiler.context); const m = new NodeModules(this.options.require, compiler.outputPath); - m.addModule(name, true); + m.addModule('@amzn/'+name, true); m.transformAll(); }); } diff --git a/js/package.json b/js/package.json index 81193e6..85ec936 100644 --- a/js/package.json +++ b/js/package.json @@ -1,5 +1,5 @@ { - "name": "apl-workspace", + "name": "@amzn/apl-workspace", "version": "1.0.0", "private": true, "workspaces": [ @@ -25,5 +25,8 @@ "clean:html": "cd apl-html && yarn clean", "clean:wasm": "cd apl-wasm && yarn clean", "lint": "yarn workspaces run lint" + }, + "npm-pretty-much": { + "legacyPackageNameAlias": "apl-workspace" } } diff --git a/js/yarn.lock b/js/yarn.lock index b6f3ceb..a40b5c9 100644 --- a/js/yarn.lock +++ b/js/yarn.lock @@ -447,9 +447,9 @@ ansi-regex@^3.0.0: integrity sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw== ansi-regex@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb843756a839ca8c7e3168c51997" - integrity sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg== + version "4.1.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.1.tgz#164daac87ab2d6f6db3a29875e2d1766582dabed" + integrity sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g== ansi-regex@^5.0.0: version "5.0.0" @@ -563,14 +563,14 @@ array-unique@^0.3.2: resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428" integrity sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg= -array.prototype.reduce@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/array.prototype.reduce/-/array.prototype.reduce-1.0.4.tgz#8167e80089f78bff70a99e20bd4201d4663b0a6f" - integrity sha512-WnM+AjG/DvLRLo4DDl+r+SvCzYtD2Jd9oeBYMcEaI7t3fFrHY9M53/wdLcTvmZNQ70IU6Htj0emFkZ5TS+lrdw== +array.prototype.reduce@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/array.prototype.reduce/-/array.prototype.reduce-1.0.5.tgz#6b20b0daa9d9734dd6bc7ea66b5bbce395471eac" + integrity sha512-kDdugMl7id9COE8R7MHF5jWk7Dqt/fs4Pv+JXoICnYwqpjjjbUurz6w5fT5IG6brLdJhv6/VoHB0H7oyIBXd+Q== dependencies: call-bind "^1.0.2" - define-properties "^1.1.3" - es-abstract "^1.19.2" + define-properties "^1.1.4" + es-abstract "^1.20.4" es-array-method-boxes-properly "^1.0.0" is-string "^1.0.7" @@ -1248,7 +1248,7 @@ color-convert@^2.0.1: color-name@1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" - integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= + integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== color-name@~1.1.4: version "1.1.4" @@ -1608,9 +1608,9 @@ decamelize@^1.1.2, decamelize@^1.2.0: integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA= decode-uri-component@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" - integrity sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU= + version "0.2.2" + resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.2.tgz#e69dbe25d37941171dd540e024c444cd5188e1e9" + integrity sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ== deep-eql@^3.0.1: version "3.0.1" @@ -1851,22 +1851,23 @@ error-ex@^1.2.0: dependencies: is-arrayish "^0.2.1" -es-abstract@^1.19.0, es-abstract@^1.19.2, es-abstract@^1.19.5, es-abstract@^1.20.1: - version "1.20.2" - resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.20.2.tgz#8495a07bc56d342a3b8ea3ab01bd986700c2ccb3" - integrity sha512-XxXQuVNrySBNlEkTYJoDNFe5+s2yIOpzq80sUHEdPdQr0S5nTLz4ZPPPswNIpKseDDUS5yghX1gfLIHQZ1iNuQ== +es-abstract@^1.19.0, es-abstract@^1.20.4: + version "1.20.5" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.20.5.tgz#e6dc99177be37cacda5988e692c3fa8b218e95d2" + integrity sha512-7h8MM2EQhsCA7pU/Nv78qOXFpD8Rhqd12gYiSJVkrH9+e8VuA8JlPJK/hQjjlLv6pJvx/z1iRFKzYb0XT/RuAQ== dependencies: call-bind "^1.0.2" es-to-primitive "^1.2.1" function-bind "^1.1.1" function.prototype.name "^1.1.5" - get-intrinsic "^1.1.2" + get-intrinsic "^1.1.3" get-symbol-description "^1.0.0" + gopd "^1.0.1" has "^1.0.3" has-property-descriptors "^1.0.0" has-symbols "^1.0.3" internal-slot "^1.0.3" - is-callable "^1.2.4" + is-callable "^1.2.7" is-negative-zero "^2.0.2" is-regex "^1.1.4" is-shared-array-buffer "^1.0.2" @@ -1876,8 +1877,9 @@ es-abstract@^1.19.0, es-abstract@^1.19.2, es-abstract@^1.19.5, es-abstract@^1.20 object-keys "^1.1.1" object.assign "^4.1.4" regexp.prototype.flags "^1.4.3" - string.prototype.trimend "^1.0.5" - string.prototype.trimstart "^1.0.5" + safe-regex-test "^1.0.0" + string.prototype.trimend "^1.0.6" + string.prototype.trimstart "^1.0.6" unbox-primitive "^1.0.2" es-array-method-boxes-properly@^1.0.0: @@ -2337,7 +2339,7 @@ get-func-name@^2.0.0: resolved "https://registry.yarnpkg.com/get-func-name/-/get-func-name-2.0.0.tgz#ead774abee72e20409433a066366023dd6887a41" integrity sha1-6td0q+5y4gQJQzoGY2YCPdaIekE= -get-intrinsic@^1.0.2, get-intrinsic@^1.1.0, get-intrinsic@^1.1.1, get-intrinsic@^1.1.2: +get-intrinsic@^1.0.2, get-intrinsic@^1.1.0, get-intrinsic@^1.1.1, get-intrinsic@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.1.3.tgz#063c84329ad93e83893c7f4f243ef63ffa351385" integrity sha512-QJVz1Tj7MS099PevUG5jvnt9tSkXN8K14dxQlikJuPt4uD9hHAHjLyLBiLR5zELelBdD9QNRAXZzsJx0WaDL9A== @@ -2519,6 +2521,13 @@ globby@^7.1.1: pify "^3.0.0" slash "^1.0.0" +gopd@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.0.1.tgz#29ff76de69dac7489b7c0918a5788e56477c332c" + integrity sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA== + dependencies: + get-intrinsic "^1.1.3" + graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0: version "4.2.4" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb" @@ -2581,7 +2590,7 @@ has-flag@^1.0.0: has-flag@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" - integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= + integrity sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw== has-flag@^4.0.0: version "4.0.0" @@ -2894,10 +2903,10 @@ is-buffer@~2.0.3: resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-2.0.5.tgz#ebc252e400d22ff8d77fa09888821a24a658c191" integrity sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ== -is-callable@^1.1.4, is-callable@^1.2.4: - version "1.2.5" - resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.5.tgz#6123e0b1fef5d7591514b371bb018204892f1a2b" - integrity sha512-ZIWRujF6MvYGkEuHMYtFRkL2wAtFw89EHfKlXrkPkjQZZRWeh9L1q3SV13NIfHnqxugjLvAOkEHx9mb1zcMnEw== +is-callable@^1.1.4, is-callable@^1.2.7: + version "1.2.7" + resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.7.tgz#3bc2a85ea742d9e36205dcacdd72ca1fdc51b055" + integrity sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA== is-ci@^2.0.0: version "2.0.0" @@ -2989,7 +2998,7 @@ is-fullwidth-code-point@^1.0.0: is-fullwidth-code-point@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" - integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8= + integrity sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w== is-fullwidth-code-point@^3.0.0: version "3.0.0" @@ -3145,7 +3154,7 @@ isarray@1.0.0, isarray@^1.0.0, isarray@~1.0.0: isexe@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" - integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= + integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== isobject@^2.0.0: version "2.1.0" @@ -3431,7 +3440,7 @@ loader-runner@^2.4.0: resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-2.4.0.tgz#ed47066bfe534d7e84c4c7b9998c2a75607d9357" integrity sha512-Jsmr89RcXGIwivFY21FcRrisYZfvLMTWx5kOLc+JTxtpBOG6xML0vzbc6SEQG2FO9/4Fc3wW4LVcB5DmGflaRw== -loader-utils@^1.0.2, loader-utils@^1.1.0, loader-utils@^1.2.3, loader-utils@^1.4.0: +loader-utils@^1.0.2, loader-utils@^1.1.0, loader-utils@^1.2.3: version "1.4.0" resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.4.0.tgz#c579b5e34cb34b1a74edc6c1fb36bfa371d5a613" integrity sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA== @@ -3440,6 +3449,15 @@ loader-utils@^1.0.2, loader-utils@^1.1.0, loader-utils@^1.2.3, loader-utils@^1.4 emojis-list "^3.0.0" json5 "^1.0.1" +loader-utils@^1.4.0: + version "1.4.2" + resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.4.2.tgz#29a957f3a63973883eb684f10ffd3d151fec01a3" + integrity sha512-I5d00Pd/jwMD2QCduo657+YM/6L3KZu++pmX9VFncxaxvHcru9jx1lBaFft+r4Mt2jK0Yhp41XlRAihzPxHNCg== + dependencies: + big.js "^5.2.2" + emojis-list "^3.0.0" + json5 "^1.0.1" + locate-path@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-3.0.0.tgz#dbec3b3ab759758071b58fe59fc41871af21400e" @@ -3704,11 +3722,16 @@ minimist@0.0.8: resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" integrity sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0= -minimist@^1.1.0, minimist@^1.1.3, minimist@^1.2.0, minimist@^1.2.5: +minimist@^1.1.0, minimist@^1.1.3: version "1.2.5" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== +minimist@^1.2.0, minimist@^1.2.5: + version "1.2.7" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.7.tgz#daa1c4d91f507390437c6a8bc01078e7000c4d18" + integrity sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g== + mississippi@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/mississippi/-/mississippi-3.0.0.tgz#ea0a3291f97e0b5e8776b363d5f0a12d94c67022" @@ -3837,11 +3860,16 @@ ms@2.1.1: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a" integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg== -ms@2.1.2, ms@^2.1.1: +ms@2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== +ms@^2.1.1: + version "2.1.3" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== + nan@^2.12.1: version "2.14.1" resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.1.tgz#d7be34dfa3105b91494c3147089315eff8874b01" @@ -4038,14 +4066,14 @@ object.assign@^4.1.4: object-keys "^1.1.1" object.getownpropertydescriptors@^2.0.3: - version "2.1.4" - resolved "https://registry.yarnpkg.com/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.4.tgz#7965e6437a57278b587383831a9b829455a4bc37" - integrity sha512-sccv3L/pMModT6dJAYF3fzGMVcb38ysQ0tEE6ixv2yXJDtEIPph268OlAdJj5/qZMZDq2g/jqvwppt36uS/uQQ== + version "2.1.5" + resolved "https://registry.yarnpkg.com/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.5.tgz#db5a9002489b64eef903df81d6623c07e5b4b4d3" + integrity sha512-yDNzckpM6ntyQiGTik1fKV1DcVDRS+w8bvpWNCBanvH5LfRX9O8WTHqQzG4RZwRAM4I0oU7TV11Lj5v0g20ibw== dependencies: - array.prototype.reduce "^1.0.4" + array.prototype.reduce "^1.0.5" call-bind "^1.0.2" define-properties "^1.1.4" - es-abstract "^1.20.1" + es-abstract "^1.20.4" object.pick@^1.3.0: version "1.3.0" @@ -4194,7 +4222,7 @@ path-exists@^2.0.0: path-exists@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515" - integrity sha1-zg6+ql94yxiSXqfYENe1mwEP1RU= + integrity sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ== path-is-absolute@^1.0.0: version "1.0.1" @@ -4694,7 +4722,7 @@ request@^2.45.0: require-directory@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" - integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I= + integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== require-main-filename@^2.0.0: version "2.0.0" @@ -4814,6 +4842,15 @@ safe-buffer@~5.1.0, safe-buffer@~5.1.1: resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== +safe-regex-test@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/safe-regex-test/-/safe-regex-test-1.0.0.tgz#793b874d524eb3640d1873aad03596db2d4f2295" + integrity sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA== + dependencies: + call-bind "^1.0.2" + get-intrinsic "^1.1.3" + is-regex "^1.1.4" + safe-regex@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/safe-regex/-/safe-regex-1.1.0.tgz#40a3669f3b077d1e943d44629e157dd48023bf2e" @@ -4881,7 +4918,7 @@ serialize-javascript@^3.1.0: set-blocking@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" - integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc= + integrity sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw== set-value@^2.0.0, set-value@^2.0.1: version "2.0.1" @@ -5117,7 +5154,7 @@ split-string@^3.0.1, split-string@^3.0.2: sprintf-js@~1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" - integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= + integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g== sshpk@^1.7.0: version "1.16.1" @@ -5216,23 +5253,23 @@ string-width@^4.2.0: is-fullwidth-code-point "^3.0.0" strip-ansi "^6.0.0" -string.prototype.trimend@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.5.tgz#914a65baaab25fbdd4ee291ca7dde57e869cb8d0" - integrity sha512-I7RGvmjV4pJ7O3kdf+LXFpVfdNOxtCW/2C8f6jNiW4+PQchwxkCDzlk1/7p+Wl4bqFIZeF47qAHXLuHHWKAxog== +string.prototype.trimend@^1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.6.tgz#c4a27fa026d979d79c04f17397f250a462944533" + integrity sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ== dependencies: call-bind "^1.0.2" define-properties "^1.1.4" - es-abstract "^1.19.5" + es-abstract "^1.20.4" -string.prototype.trimstart@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.5.tgz#5466d93ba58cfa2134839f81d7f42437e8c01fef" - integrity sha512-THx16TJCGlsN0o6dl2o6ncWUsdgnLRSA23rRE5pyGBw/mLr3Ej/R2LaqCtgP8VNMGZsvMWnf9ooZPyY2bHvUFg== +string.prototype.trimstart@^1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.6.tgz#e90ab66aa8e4007d92ef591bbf3cd422c56bdcf4" + integrity sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA== dependencies: call-bind "^1.0.2" define-properties "^1.1.4" - es-abstract "^1.19.5" + es-abstract "^1.20.4" string_decoder@^1.0.0, string_decoder@^1.1.1: version "1.3.0" @@ -5263,7 +5300,7 @@ strip-ansi@^3.0.0, strip-ansi@^3.0.1: strip-ansi@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f" - integrity sha1-qEeQIusaw2iocTibY1JixQXuNo8= + integrity sha512-4XaJ2zQdCzROZDivEVIDPkcQn8LMFSa8kj8Gxb/Lnwzv9A8VctNZ+lfivC/sV3ivW8ElJTERXZoPBRrZKkNKow== dependencies: ansi-regex "^3.0.0" @@ -5966,7 +6003,7 @@ which-boxed-primitive@^1.0.2: which-module@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" - integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho= + integrity sha512-B+enWhmw6cjfVC7kS8Pj9pCrKSc5txArRyaYGe088shv/FGWH+0Rjx/xPgtsWfsUtS27FkP697E4DDhgrgoc0Q== which@1.3.1, which@^1.1.1, which@^1.2.14, which@^1.2.9, which@^1.3.1: version "1.3.1" diff --git a/package.json b/package.json index ed5857e..723f587 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "apl-viewhost-web", - "version": "2022.2.0", + "version": "2023.1.0", "description": "This is a Web-assembly version (WASM) of apl viewhost web.", "license": "Apache 2.0", "repository": { diff --git a/scripts/fetch.js b/scripts/fetch.js index 6b10f4c..5b69509 100644 --- a/scripts/fetch.js +++ b/scripts/fetch.js @@ -3,7 +3,7 @@ const https = require('https'); const fs = require('fs'); -const artifactUrl = 'https://d1gkjrhppbyzyh.cloudfront.net/apl-viewhost-web/6dcd129a-c82d-46fe-ab8e-93b6f3ed1870/index.js'; +const artifactUrl = 'https://d1gkjrhppbyzyh.cloudfront.net/apl-viewhost-web/92777dcb-9ef0-4824-ba45-18b1505eb190/index.js'; const outputFilePath = 'index.js'; const outputFile = fs.createWriteStream(outputFilePath); diff --git a/wasm/CMakeLists.txt b/wasm/CMakeLists.txt index 174fc16..82c7c65 100644 --- a/wasm/CMakeLists.txt +++ b/wasm/CMakeLists.txt @@ -115,6 +115,7 @@ add_custom_target(generate-wasm-enums ALL -f "SubmitKeyType" -f "TextAlign" -f "TextAlignVertical" + -f "TextTrackType" -f "TrackState" -f "UpdateType" -f "VectorGraphicScale" diff --git a/wasm/config.cmake b/wasm/config.cmake index f249301..42ddf84 100644 --- a/wasm/config.cmake +++ b/wasm/config.cmake @@ -44,5 +44,5 @@ if(WASM_PROFILING) endif() #set compiler flags -set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${WASM_FLAGS} --bind") -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${WASM_FLAGS} --bind") +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${WASM_FLAGS} --bind -O1") +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${WASM_FLAGS} --bind -O1") diff --git a/wasm/include/wasm/graphicpattern.h b/wasm/include/wasm/graphicpattern.h new file mode 100644 index 0000000..f7138af --- /dev/null +++ b/wasm/include/wasm/graphicpattern.h @@ -0,0 +1,29 @@ +/** + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef APL_WASM_GRAPHIC_PATTERN_H +#define APL_WASM_GRAPHIC_PATTERN_H + +#include "apl/apl.h" +#include + +namespace apl { +namespace wasm { + +namespace internal { +struct GraphicPatternMethods { + static std::string getId(const GraphicPatternPtr& graphicPattern); + static std::string getDescription(const GraphicPatternPtr& graphicPattern); + static double getHeight(const GraphicPatternPtr& graphicPattern); + static double getWidth(const GraphicPatternPtr& graphicPattern); + static size_t getItemCount(const GraphicPatternPtr& graphicPattern); + static GraphicElementPtr getItemAt(const GraphicPatternPtr& graphicPattern, size_t index); +}; +} // namespace internal + +} // namespace wasm +} // namespace apl + +#endif // APL_WASM_GRAPHIC_PATTERN_H \ No newline at end of file diff --git a/wasm/src/audioplayer.cpp b/wasm/src/audioplayer.cpp index 92238f7..b71db7d 100644 --- a/wasm/src/audioplayer.cpp +++ b/wasm/src/audioplayer.cpp @@ -81,7 +81,7 @@ static inline apl::SpeechMarkType stringToMarkType(const std::string& type) { auto result = apl::SpeechMarkType::kSpeechMarkUnknown; if (type == "word") { result = apl::SpeechMarkType::kSpeechMarkWord; - } else if (type == "visime") { + } else if (type == "viseme") { result = apl::SpeechMarkType::kSpeechMarkViseme; } else if (type == "sentence") { result = apl::SpeechMarkType::kSpeechMarkSentence; diff --git a/wasm/src/context.cpp b/wasm/src/context.cpp index 88cc5d3..b9eb684 100644 --- a/wasm/src/context.cpp +++ b/wasm/src/context.cpp @@ -268,10 +268,10 @@ ContextMethods::create(emscripten::val options, emscripten::val text, emscripten // get document background, color or gradient background.set("color", emscripten::val(Color().asString())); // Transparent background.set("gradient", emscripten::val::null()); - if (contentPtr->getBackground(coreMetrics, rootConfig).isColor()) { + if (contentPtr->getBackground(coreMetrics, rootConfig).is()) { background.set("color", emscripten::val(contentPtr->getBackground(coreMetrics, rootConfig).asColor().asString())); - } else if (contentPtr->getBackground(coreMetrics, rootConfig).isGradient()) { - background.set("gradient", emscripten::getValFromObject(contentPtr->getBackground(coreMetrics, rootConfig).getGradient(), m)); + } else if (contentPtr->getBackground(coreMetrics, rootConfig).is()) { + background.set("gradient", emscripten::getValFromObject(contentPtr->getBackground(coreMetrics, rootConfig).get(), m)); } // add metrics to the root context so we can connect our viewport to any events, components, diff --git a/wasm/src/embindutils.cpp b/wasm/src/embindutils.cpp index b72824e..dd5ef7a 100644 --- a/wasm/src/embindutils.cpp +++ b/wasm/src/embindutils.cpp @@ -21,59 +21,58 @@ iterateProps(const apl::CalculatedPropertyMap& calculated, emscripten::val& map, emscripten::val getValFromObject(const apl::Object& prop, WASMMetrics* m) { - double scale; - switch (prop.getType()) { - case apl::Object::ObjectType::kNumberType: - return emscripten::val(prop.asNumber()); - case apl::Object::ObjectType::kStringType: - return emscripten::val(prop.asString()); - case apl::Object::ObjectType::kBoolType: - return emscripten::val(prop.asBoolean()); - case apl::Object::ObjectType::kColorType: - return emscripten::val(prop.asColor().get()); - case apl::Object::ObjectType::kAbsoluteDimensionType: - scale = m->toViewhost(1.0f); - return emscripten::val(prop.getAbsoluteDimension() * scale); - case apl::Object::ObjectType::kFilterType: - return getValFromObject(prop.getFilter(), m); - case apl::Object::ObjectType::kRadiiType: - return getValFromObject(prop.getRadii(), m); - case apl::Object::ObjectType::kRectType: - return getValFromObject(prop.getRect(), m); - case apl::Object::ObjectType::kGradientType: - return getValFromObject(prop.getGradient(), m); - case apl::Object::ObjectType::kGraphicFilterType: - return getValFromObject(prop.getGraphicFilter(), m); - case apl::Object::ObjectType::kGraphicPatternType: - return emscripten::val(prop.getGraphicPattern()); - case apl::Object::ObjectType::kMediaSourceType: - return getValFromObject(prop.getMediaSource(), m); - case apl::Object::ObjectType::kMapType: - return getValFromObject(prop.getMap(), m); - case apl::Object::ObjectType::kArrayType: - return getValFromObject(prop.getArray(), m); - case apl::Object::ObjectType::kStyledTextType: - return getValFromObject(prop.getStyledText(), m); - case apl::Object::ObjectType::kGraphicType: { - // set the metrics here because we need them for scaling - // a graphic element, which is derived from a graphic. - auto graphic = prop.getGraphic(); - graphic->setUserData(m); - return emscripten::val(graphic); - } - case apl::Object::ObjectType::kTransform2DType: { - auto transform = prop.getTransform2D().get(); - auto mat = "matrix(" + std::to_string(transform[0]) + "," + - std::to_string(transform[1]) + "," + std::to_string(transform[2]) + "," + - std::to_string(transform[3]) + "," + std::to_string(transform[4]) + - "," + std::to_string(transform[5]) + ")"; - return emscripten::val(mat); - } - case apl::Object::ObjectType::kURLRequestType: - return getValFromObject(prop.getURLRequest(), m); - default: - return emscripten::val::undefined(); + if (prop.isNumber()) + return emscripten::val(prop.getDouble()); + else if (prop.isBoolean()) + return emscripten::val(prop.getBoolean()); + else if (prop.isString()) + return emscripten::val(prop.getString()); + else if (prop.is()) + return emscripten::val(prop.getColor()); + else if (prop.isAbsoluteDimension()) { + auto scale = m->toViewhost(1.0f); + return emscripten::val(prop.getAbsoluteDimension() * scale); } + else if (prop.is()) + return getValFromObject(prop.get(), m); + else if (prop.is()) + return getValFromObject(prop.get(), m); + else if (prop.is()) + return getValFromObject(prop.get(), m); + else if (prop.is()) + return getValFromObject(prop.get(), m); + else if (prop.is()) + return getValFromObject(prop.get(), m); + else if (prop.is()) + return emscripten::val(prop.get()); + else if (prop.is()) + return getValFromObject(prop.get(), m); + else if (prop.isMap()) + return getValFromObject(prop.getMap(), m); + else if (prop.isArray()) + return getValFromObject(prop.getArray(), m); + else if (prop.is()) + return getValFromObject(prop.get(), m); + else if (prop.is()) { + // set the metrics here because we need them for scaling + // a graphic element, which is derived from a graphic. + auto graphic = prop.get(); + graphic->setUserData(m); + return emscripten::val(graphic); + } + else if (prop.is()) { + auto transform = prop.get().get(); + auto mat = "matrix(" + std::to_string(transform[0]) + "," + + std::to_string(transform[1]) + "," + std::to_string(transform[2]) + "," + + std::to_string(transform[3]) + "," + std::to_string(transform[4]) + + "," + std::to_string(transform[5]) + ")"; + return emscripten::val(mat); + } + else if (prop.is()) + return getValFromObject(prop.get(), m); + + return emscripten::val::undefined(); + } emscripten::val diff --git a/wasm/src/mediaplayer.cpp b/wasm/src/mediaplayer.cpp index 21e69ab..25e385b 100644 --- a/wasm/src/mediaplayer.cpp +++ b/wasm/src/mediaplayer.cpp @@ -11,6 +11,15 @@ namespace apl { namespace wasm { +static std::string textTrackTypeToKind(TextTrackType type) { + switch (type) { + case TextTrackType::kTextTrackTypeCaption: + return "captions"; + default: + return "unsupported"; + } +} + std::shared_ptr MediaPlayer::create(apl::MediaPlayerCallback&& playerCallback, emscripten::val MediaPlayerFactory) @@ -58,16 +67,27 @@ MediaPlayer::setTrackList(std::vector tracks) emscripten::val trackArray = emscripten::val::array(); - int i = 0; - for (auto& track : tracks) { + for (int i = 0; i < tracks.size(); i++) { + auto& track = tracks[i]; emscripten::val trackObj = emscripten::val::object(); trackObj.set("url", track.url); trackObj.set("offset", track.offset); trackObj.set("duration", track.duration); trackObj.set("repeatCount", track.repeatCount); + emscripten::val textTrackArray = emscripten::val::array(); + for (int j = 0; j < track.textTracks.size(); j++) { + auto& textTrack = track.textTracks[j]; + emscripten::val textTrackObj = emscripten::val::object(); + textTrackObj.set("kind", textTrackTypeToKind(textTrack.type)); + textTrackObj.set("url", textTrack.url); + textTrackObj.set("description", textTrack.description); + + textTrackArray.set(j, textTrackObj); + } + trackObj.set("textTracks", textTrackArray); + trackArray.set(i, trackObj); - i++; } mPlayer.call("setTrackList", trackArray); @@ -207,7 +227,8 @@ void MediaPlayer::doCallback(int eventType) { if (!isActive()) return; apl::MediaPlayerEventType mediaPlayerEventType = static_cast(eventType); - if (mediaPlayerEventType == apl::MediaPlayerEventType::kMediaPlayerEventEnd) { + if (mediaPlayerEventType == apl::MediaPlayerEventType::kMediaPlayerEventEnd + || eventType == apl::MediaPlayerEventType::kMediaPlayerEventTrackFail) { resolveExistingAction(); } auto callback = mCallback;