Skip to content

Commit

Permalink
July 2023 Release of the APL 2023.2 compliant APL Viewhost Web
Browse files Browse the repository at this point in the history
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
  • Loading branch information
amzn-admfox committed Jul 6, 2023
1 parent 395ca65 commit f99f5b4
Show file tree
Hide file tree
Showing 19 changed files with 94 additions and 63 deletions.
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
# Changelog for apl-viewhost-web

## [2023.2]
This release adds support for version 2023.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)

### Added
- Add support for the seekTo ControlMedia command

### Changed
- Remove usage of APL Core Library's deprecated getTheme API
- Bug fixes

## [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)

Expand Down
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
# Alexa Presentation Language (APL) Viewhost Web

<p>
<a href="https://github.com/alexa/apl-viewhost-web/tree/v2023.1.0" alt="version">
<img src="https://img.shields.io/badge/stable%20version-2023.1.0-brightgreen" /></a>
<a href="https://github.com/alexa/apl-core-library/tree/v2023.1.0" alt="APLCore">
<img src="https://img.shields.io/badge/apl%20core%20library-2023.1.0-navy" /></a>
<a href="https://github.com/alexa/apl-viewhost-web/tree/v2023.2.0" alt="version">
<img src="https://img.shields.io/badge/stable%20version-2023.2.0-brightgreen" /></a>
<a href="https://github.com/alexa/apl-core-library/tree/v2023.2.0" alt="APLCore">
<img src="https://img.shields.io/badge/apl%20core%20library-2023.2.0-navy" /></a>
</p>

## Introduction
Expand All @@ -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 16.x or higher
* [NodeJS](https://nodejs.org/en/) - version 14.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)

Expand Down
2 changes: 0 additions & 2 deletions js/apl-html/lib/dts/Context.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,6 @@ declare namespace APL {

public topComponent(): APL.Component;

public getTheme(): string;

public getBackground(): APL.IBackground;

public setBackground(background: APL.IBackground): void;
Expand Down
20 changes: 7 additions & 13 deletions js/apl-html/src/APLRenderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -527,13 +527,7 @@ export default abstract class APLRenderer<Options = any> {
});
}

let docTheme: string = this.context.getTheme();
if (docTheme !== 'light' && docTheme !== 'dark') {
// treat themes other than dark and light as dark
docTheme = 'dark';
}

this.setBackground(docTheme);
this.setBackground();

// begin update loop
this.requestId = requestAnimationFrame(this.update);
Expand Down Expand Up @@ -593,15 +587,15 @@ export default abstract class APLRenderer<Options = any> {
return Object.keys(this.componentMap).length;
}

private setBackground(docTheme: string) {
private setBackground() {
// Setting backgroundColor to black to ensure the correct behaviour
// of a gradient containing an alpha channel component

this.view.style.backgroundColor = 'black';

const background = this.context.getBackground();
const backgroundColors = {
dark: 'black',
light: 'white'
};
// Spec: If the background property is partially transparent
// the default background color of the device will show through
this.view.style.backgroundColor = backgroundColors[docTheme];
this.view.style.backgroundImage = background.gradient ?
getCssGradient(background.gradient, this.logger) :
getCssPureColorGradient(background.color);
Expand Down
5 changes: 1 addition & 4 deletions js/apl-html/src/components/EditText.ts
Original file line number Diff line number Diff line change
Expand Up @@ -263,10 +263,7 @@ export class EditText extends ActionableComponent<IEditTextProperties> {
}

private setInputText = async () => {
const text = await this.filterText(this.props[PropertyKey.kPropertyText]);
if (text.length > 0) {
this.inputElement.value = text;
}
this.inputElement.value = await this.filterText(this.props[PropertyKey.kPropertyText]);
}

public focus = () => {
Expand Down
2 changes: 2 additions & 0 deletions js/apl-html/src/media/IMediaPlayerHandle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ export interface IMediaPlayerHandle extends IMediaEventListener {

seek(offset: number): Promise<any>;

seekTo(position: number): Promise<any>;

play(waitForFinish: boolean): Promise<any>;

pause(): Promise<any>;
Expand Down
52 changes: 30 additions & 22 deletions js/apl-html/src/media/MediaEventProcessor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -193,31 +193,15 @@ export function createMediaEventProcessor(mediaEventProcessorArgs: MediaEventPro
},
async seek({ seekOffset, fromEvent }): Promise<any> {
await ensureLoaded.call(this, fromEvent);
const mediaResource: IMediaResource = this.playbackManager.getCurrent();
const mediaOffsetMs: number = mediaResource.offset;
const currentPlaybackPositionMs: number = toMillisecondsFromSeconds(
this.player.getCurrentPlaybackPositionInSeconds()
);
const desiredPlaybackPositionMs: number = currentPlaybackPositionMs + seekOffset;
const videoDurationMs = toMillisecondsFromSeconds(this.player.getDurationInSeconds());
const isNonDefaultDuration: boolean = mediaResource.duration > 0;
const isCurrentPositionOutOfBounds: boolean =
videoDurationMs <= desiredPlaybackPositionMs;

if (isCurrentPositionOutOfBounds) {
// minus unit time otherwise will rollover to start
if (isNonDefaultDuration) {
this.player.setCurrentTimeInSeconds(mediaOffsetMs +
toSecondsFromMilliseconds(mediaResource.duration) - 0.001);
} else {
this.player.setCurrentTimeInSeconds(toSecondsFromMilliseconds(videoDurationMs) - 0.001);
}
} else if (desiredPlaybackPositionMs < mediaOffsetMs) {
this.player.setCurrentTimeInSeconds(toSecondsFromMilliseconds(mediaOffsetMs));
} else {
this.player.setCurrentTimeInSeconds(toSecondsFromMilliseconds(desiredPlaybackPositionMs));
}

setPlayerPosition(this.player, this.playbackManager.getCurrent(), currentPlaybackPositionMs + seekOffset);
this.updateMediaState(fromEvent);
},
async seekTo({ position, fromEvent }): Promise<any> {
await ensureLoaded.call(this, fromEvent);
setPlayerPosition(this.player, this.playbackManager.getCurrent(), position);
this.updateMediaState(fromEvent);
},
async rewind({ fromEvent }): Promise<any> {
Expand Down Expand Up @@ -438,3 +422,27 @@ function ensureValidMediaState(mediaState: any): mediaState is APL.IMediaState {
function isValidMediaStateValue(n: any): n is number {
return !Number.isNaN(n) && n !== undefined;
}

function setPlayerPosition(player: any, mediaResource: IMediaResource, desiredPlaybackPositionMs: number) {
const mediaOffsetMs: number = mediaResource.offset;
const videoDurationMs = toMillisecondsFromSeconds(player.getDurationInSeconds());
const providedVideoDurationMs = mediaResource.duration;
const isDurationProvided: boolean = mediaResource.duration !== 0;

// minus unit time for EOF otherwise will rollover to start
const endOfFileMs = videoDurationMs - 1;
const endOfOffsetAndDurationMs = mediaOffsetMs + providedVideoDurationMs - 1;

// Calculate the range of the clipped track based on `offset` and `duration` values
const trueStart = Math.max(0, mediaOffsetMs);
const trueEnd = (isDurationProvided)
? Math.min(endOfFileMs, endOfOffsetAndDurationMs)
: endOfFileMs;

player.setCurrentTimeInSeconds(toSecondsFromMilliseconds(
Math.min(
Math.max(desiredPlaybackPositionMs, trueStart),
trueEnd
)
));
}
1 change: 1 addition & 0 deletions js/apl-html/src/media/MediaEventSequencer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export enum VideoInterface {
PAUSE = 'pause',
STOP = 'stop',
SEEK = 'seek',
SEEKTO = 'seekTo',
REWIND = 'rewind',
PREVIOUS = 'previous',
NEXT = 'next',
Expand Down
7 changes: 7 additions & 0 deletions js/apl-html/src/media/MediaPlayerHandle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,13 @@ export class MediaPlayerHandle implements IMediaPlayerHandle, IMediaEventListene
});
}

public async seekTo(position: number): Promise<any> {
this.eventSequencer.enqueueForProcessing(VideoInterface.SEEKTO, {
position,
fromEvent: true
});
}

public async play(waitForFinish: boolean): Promise<any> {
// Route through video component so can be override
if (!this.videoComponent) {
Expand Down
1 change: 1 addition & 0 deletions js/apl-html/src/media/Resource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ export enum ControlMediaCommandName {
PREVIOUS = 'previous',
REWIND = 'rewind',
SEEK = 'seek',
SEEKTO = 'seekTo',
SETTRACK = 'setTrack'
}

Expand Down
5 changes: 4 additions & 1 deletion js/apl-html/src/media/audio/Id3Parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,10 @@ const parseFirstTXXXFrame = (buffer : Uint8Array, offset : number) : IBaseMarker
// Slice selects from the start byte, and ends at HEADER_LENGTH + length - 1
// We need to skip past the header and frame length to get to the end
const contents = buffer.slice(start, offset + HEADER_LENGTH + length - 1);
const data = String.fromCharCode.apply(null, contents);

// TODO: After we upgrade to Typescript > 2.8.0, this can be changed to: new TextDecoder('utf-8')
const textDecoder = new (window as any).TextDecoder('utf-8');
const data = textDecoder.decode(contents)
return JSON.parse(data);
};

Expand Down
4 changes: 3 additions & 1 deletion js/apl-html/src/utils/AplVersionUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ 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_2023_2 = 13;
export const APL_LATEST = Number.MAX_VALUE;

export interface AplVersionUtils {
Expand All @@ -38,7 +39,8 @@ export function createAplVersionUtils(): AplVersionUtils {
['1.9', APL_1_9],
['2022.1', APL_2022_1],
['2022.2', APL_2022_2],
['2023.1', APL_2023_1]
['2023.1', APL_2023_1],
['2023.2', APL_2023_2]
]);

return {
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "apl-viewhost-web",
"version": "2023.1.0",
"version": "2023.2.0",
"description": "This is a Web-assembly version (WASM) of apl viewhost web.",
"license": "Apache 2.0",
"repository": {
Expand Down
2 changes: 1 addition & 1 deletion scripts/fetch.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
const https = require('https');
const fs = require('fs');

const artifactUrl = 'https://d1gkjrhppbyzyh.cloudfront.net/apl-viewhost-web/92777dcb-9ef0-4824-ba45-18b1505eb190/index.js';
const artifactUrl = 'https://d1gkjrhppbyzyh.cloudfront.net/apl-viewhost-web/ed26327f-31c5-4296-8dee-2bc2d159b901/index.js';

const outputFilePath = 'index.js';
const outputFile = fs.createWriteStream(outputFilePath);
Expand Down
13 changes: 11 additions & 2 deletions wasm/config.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -44,5 +44,14 @@ if(WASM_PROFILING)
endif()

#set compiler flags
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${WASM_FLAGS} --bind -O1")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${WASM_FLAGS} --bind -O1")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${WASM_FLAGS} --bind")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${WASM_FLAGS} --bind")

# Set optimization level from build type
if(CMAKE_BUILD_TYPE MATCHES DEBUG)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O1")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O1")
else()
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O3")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O3")
endif()
1 change: 0 additions & 1 deletion wasm/include/wasm/context.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ struct ContextMethods {
static apl::RootContextPtr create(emscripten::val options, emscripten::val text, emscripten::val metrics, emscripten::val content, emscripten::val config, emscripten::val scalingOptions);

static apl::ComponentPtr topComponent(const apl::RootContextPtr& context);
static std::string getTheme(const apl::RootContextPtr& context);
static emscripten::val getBackground(const apl::RootContextPtr& context);
static void setBackground(const apl::RootContextPtr& context, emscripten::val background);
static std::string getDataSourceContext(const apl::RootContextPtr& context);
Expand Down
1 change: 1 addition & 0 deletions wasm/include/wasm/mediaplayer.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ class MediaPlayer : public apl::MediaPlayer {
void previous() override;
void rewind() override;
void seek(int offset) override;
void seekTo(int position) override;
void setTrackIndex(int trackIndex) override;
void setAudioTrack(apl::AudioTrack audioTrack) override;
void setMute(bool mute) override;
Expand Down
10 changes: 0 additions & 10 deletions wasm/src/context.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,15 +33,6 @@ ContextMethods::topComponent(const apl::RootContextPtr& context) {
return top;
}

std::string
ContextMethods::getTheme(const apl::RootContextPtr& context) {
std::string theme = "";
if (context) {
theme = context->getTheme();
}
return theme;
}

emscripten::val
ContextMethods::getBackground(const apl::RootContextPtr& context) {
return background;
Expand Down Expand Up @@ -504,7 +495,6 @@ EMSCRIPTEN_BINDINGS(apl_wasm_context) {
emscripten::class_<apl::RootContext>("Context")
.smart_ptr<apl::RootContextPtr>("ContextPtr")
.function("topComponent", &internal::ContextMethods::topComponent)
.function("getTheme", &internal::ContextMethods::getTheme)
.function("getBackground", &internal::ContextMethods::getBackground)
.function("setBackground", &internal::ContextMethods::setBackground)
.function("getDataSourceContext", &internal::ContextMethods::getDataSourceContext)
Expand Down
9 changes: 9 additions & 0 deletions wasm/src/mediaplayer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,15 @@ MediaPlayer::seek(int offset)
mPlayer.call<void>("seek", offset);
}

void
MediaPlayer::seekTo(int position)
{
if (!isActive()) return;
resolveExistingAction();

mPlayer.call<void>("seekTo", position);
}

void
MediaPlayer::setTrackIndex(int trackIndex)
{
Expand Down

0 comments on commit f99f5b4

Please sign in to comment.