Skip to content

Commit f99f5b4

Browse files
committed
July 2023 Release of the APL 2023.2 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
1 parent 395ca65 commit f99f5b4

19 files changed

+94
-63
lines changed

CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,15 @@
11
# Changelog for apl-viewhost-web
22

3+
## [2023.2]
4+
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)
5+
6+
### Added
7+
- Add support for the seekTo ControlMedia command
8+
9+
### Changed
10+
- Remove usage of APL Core Library's deprecated getTheme API
11+
- Bug fixes
12+
313
## [2023.1]
414
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)
515

README.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
# Alexa Presentation Language (APL) Viewhost Web
22

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

1010
## Introduction
@@ -16,7 +16,7 @@ platform or framework for which the view host was designed by leveraging the fun
1616

1717
### Prerequisites
1818

19-
* [NodeJS](https://nodejs.org/en/) - version 16.x or higher
19+
* [NodeJS](https://nodejs.org/en/) - version 14.x or higher
2020
* [cmake](https://cmake.org/install/) - the easiest way to install on Mac is using `brew install cmake`
2121
* [Yarn](https://yarnpkg.com/getting-started/install)
2222

js/apl-html/lib/dts/Context.d.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,6 @@ declare namespace APL {
3838

3939
public topComponent(): APL.Component;
4040

41-
public getTheme(): string;
42-
4341
public getBackground(): APL.IBackground;
4442

4543
public setBackground(background: APL.IBackground): void;

js/apl-html/src/APLRenderer.ts

Lines changed: 7 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -527,13 +527,7 @@ export default abstract class APLRenderer<Options = any> {
527527
});
528528
}
529529

530-
let docTheme: string = this.context.getTheme();
531-
if (docTheme !== 'light' && docTheme !== 'dark') {
532-
// treat themes other than dark and light as dark
533-
docTheme = 'dark';
534-
}
535-
536-
this.setBackground(docTheme);
530+
this.setBackground();
537531

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

596-
private setBackground(docTheme: string) {
590+
private setBackground() {
591+
// Setting backgroundColor to black to ensure the correct behaviour
592+
// of a gradient containing an alpha channel component
593+
594+
this.view.style.backgroundColor = 'black';
595+
597596
const background = this.context.getBackground();
598-
const backgroundColors = {
599-
dark: 'black',
600-
light: 'white'
601-
};
602597
// Spec: If the background property is partially transparent
603598
// the default background color of the device will show through
604-
this.view.style.backgroundColor = backgroundColors[docTheme];
605599
this.view.style.backgroundImage = background.gradient ?
606600
getCssGradient(background.gradient, this.logger) :
607601
getCssPureColorGradient(background.color);

js/apl-html/src/components/EditText.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -263,10 +263,7 @@ export class EditText extends ActionableComponent<IEditTextProperties> {
263263
}
264264

265265
private setInputText = async () => {
266-
const text = await this.filterText(this.props[PropertyKey.kPropertyText]);
267-
if (text.length > 0) {
268-
this.inputElement.value = text;
269-
}
266+
this.inputElement.value = await this.filterText(this.props[PropertyKey.kPropertyText]);
270267
}
271268

272269
public focus = () => {

js/apl-html/src/media/IMediaPlayerHandle.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ export interface IMediaPlayerHandle extends IMediaEventListener {
1616

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

19+
seekTo(position: number): Promise<any>;
20+
1921
play(waitForFinish: boolean): Promise<any>;
2022

2123
pause(): Promise<any>;

js/apl-html/src/media/MediaEventProcessor.ts

Lines changed: 30 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -193,31 +193,15 @@ export function createMediaEventProcessor(mediaEventProcessorArgs: MediaEventPro
193193
},
194194
async seek({ seekOffset, fromEvent }): Promise<any> {
195195
await ensureLoaded.call(this, fromEvent);
196-
const mediaResource: IMediaResource = this.playbackManager.getCurrent();
197-
const mediaOffsetMs: number = mediaResource.offset;
198196
const currentPlaybackPositionMs: number = toMillisecondsFromSeconds(
199197
this.player.getCurrentPlaybackPositionInSeconds()
200198
);
201-
const desiredPlaybackPositionMs: number = currentPlaybackPositionMs + seekOffset;
202-
const videoDurationMs = toMillisecondsFromSeconds(this.player.getDurationInSeconds());
203-
const isNonDefaultDuration: boolean = mediaResource.duration > 0;
204-
const isCurrentPositionOutOfBounds: boolean =
205-
videoDurationMs <= desiredPlaybackPositionMs;
206-
207-
if (isCurrentPositionOutOfBounds) {
208-
// minus unit time otherwise will rollover to start
209-
if (isNonDefaultDuration) {
210-
this.player.setCurrentTimeInSeconds(mediaOffsetMs +
211-
toSecondsFromMilliseconds(mediaResource.duration) - 0.001);
212-
} else {
213-
this.player.setCurrentTimeInSeconds(toSecondsFromMilliseconds(videoDurationMs) - 0.001);
214-
}
215-
} else if (desiredPlaybackPositionMs < mediaOffsetMs) {
216-
this.player.setCurrentTimeInSeconds(toSecondsFromMilliseconds(mediaOffsetMs));
217-
} else {
218-
this.player.setCurrentTimeInSeconds(toSecondsFromMilliseconds(desiredPlaybackPositionMs));
219-
}
220-
199+
setPlayerPosition(this.player, this.playbackManager.getCurrent(), currentPlaybackPositionMs + seekOffset);
200+
this.updateMediaState(fromEvent);
201+
},
202+
async seekTo({ position, fromEvent }): Promise<any> {
203+
await ensureLoaded.call(this, fromEvent);
204+
setPlayerPosition(this.player, this.playbackManager.getCurrent(), position);
221205
this.updateMediaState(fromEvent);
222206
},
223207
async rewind({ fromEvent }): Promise<any> {
@@ -438,3 +422,27 @@ function ensureValidMediaState(mediaState: any): mediaState is APL.IMediaState {
438422
function isValidMediaStateValue(n: any): n is number {
439423
return !Number.isNaN(n) && n !== undefined;
440424
}
425+
426+
function setPlayerPosition(player: any, mediaResource: IMediaResource, desiredPlaybackPositionMs: number) {
427+
const mediaOffsetMs: number = mediaResource.offset;
428+
const videoDurationMs = toMillisecondsFromSeconds(player.getDurationInSeconds());
429+
const providedVideoDurationMs = mediaResource.duration;
430+
const isDurationProvided: boolean = mediaResource.duration !== 0;
431+
432+
// minus unit time for EOF otherwise will rollover to start
433+
const endOfFileMs = videoDurationMs - 1;
434+
const endOfOffsetAndDurationMs = mediaOffsetMs + providedVideoDurationMs - 1;
435+
436+
// Calculate the range of the clipped track based on `offset` and `duration` values
437+
const trueStart = Math.max(0, mediaOffsetMs);
438+
const trueEnd = (isDurationProvided)
439+
? Math.min(endOfFileMs, endOfOffsetAndDurationMs)
440+
: endOfFileMs;
441+
442+
player.setCurrentTimeInSeconds(toSecondsFromMilliseconds(
443+
Math.min(
444+
Math.max(desiredPlaybackPositionMs, trueStart),
445+
trueEnd
446+
)
447+
));
448+
}

js/apl-html/src/media/MediaEventSequencer.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ export enum VideoInterface {
1414
PAUSE = 'pause',
1515
STOP = 'stop',
1616
SEEK = 'seek',
17+
SEEKTO = 'seekTo',
1718
REWIND = 'rewind',
1819
PREVIOUS = 'previous',
1920
NEXT = 'next',

js/apl-html/src/media/MediaPlayerHandle.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,13 @@ export class MediaPlayerHandle implements IMediaPlayerHandle, IMediaEventListene
7474
});
7575
}
7676

77+
public async seekTo(position: number): Promise<any> {
78+
this.eventSequencer.enqueueForProcessing(VideoInterface.SEEKTO, {
79+
position,
80+
fromEvent: true
81+
});
82+
}
83+
7784
public async play(waitForFinish: boolean): Promise<any> {
7885
// Route through video component so can be override
7986
if (!this.videoComponent) {

js/apl-html/src/media/Resource.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ export enum ControlMediaCommandName {
6363
PREVIOUS = 'previous',
6464
REWIND = 'rewind',
6565
SEEK = 'seek',
66+
SEEKTO = 'seekTo',
6667
SETTRACK = 'setTrack'
6768
}
6869

js/apl-html/src/media/audio/Id3Parser.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,10 @@ const parseFirstTXXXFrame = (buffer : Uint8Array, offset : number) : IBaseMarker
111111
// Slice selects from the start byte, and ends at HEADER_LENGTH + length - 1
112112
// We need to skip past the header and frame length to get to the end
113113
const contents = buffer.slice(start, offset + HEADER_LENGTH + length - 1);
114-
const data = String.fromCharCode.apply(null, contents);
114+
115+
// TODO: After we upgrade to Typescript > 2.8.0, this can be changed to: new TextDecoder('utf-8')
116+
const textDecoder = new (window as any).TextDecoder('utf-8');
117+
const data = textDecoder.decode(contents)
115118
return JSON.parse(data);
116119
};
117120

js/apl-html/src/utils/AplVersionUtils.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ export const APL_1_9 = 9;
1717
export const APL_2022_1 = 10;
1818
export const APL_2022_2 = 11;
1919
export const APL_2023_1 = 12;
20+
export const APL_2023_2 = 13;
2021
export const APL_LATEST = Number.MAX_VALUE;
2122

2223
export interface AplVersionUtils {
@@ -38,7 +39,8 @@ export function createAplVersionUtils(): AplVersionUtils {
3839
['1.9', APL_1_9],
3940
['2022.1', APL_2022_1],
4041
['2022.2', APL_2022_2],
41-
['2023.1', APL_2023_1]
42+
['2023.1', APL_2023_1],
43+
['2023.2', APL_2023_2]
4244
]);
4345

4446
return {

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "apl-viewhost-web",
3-
"version": "2023.1.0",
3+
"version": "2023.2.0",
44
"description": "This is a Web-assembly version (WASM) of apl viewhost web.",
55
"license": "Apache 2.0",
66
"repository": {

scripts/fetch.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
const https = require('https');
44
const fs = require('fs');
55

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

88
const outputFilePath = 'index.js';
99
const outputFile = fs.createWriteStream(outputFilePath);

wasm/config.cmake

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,5 +44,14 @@ if(WASM_PROFILING)
4444
endif()
4545

4646
#set compiler flags
47-
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${WASM_FLAGS} --bind -O1")
48-
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${WASM_FLAGS} --bind -O1")
47+
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${WASM_FLAGS} --bind")
48+
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${WASM_FLAGS} --bind")
49+
50+
# Set optimization level from build type
51+
if(CMAKE_BUILD_TYPE MATCHES DEBUG)
52+
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O1")
53+
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O1")
54+
else()
55+
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O3")
56+
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O3")
57+
endif()

wasm/include/wasm/context.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ struct ContextMethods {
2626
static apl::RootContextPtr create(emscripten::val options, emscripten::val text, emscripten::val metrics, emscripten::val content, emscripten::val config, emscripten::val scalingOptions);
2727

2828
static apl::ComponentPtr topComponent(const apl::RootContextPtr& context);
29-
static std::string getTheme(const apl::RootContextPtr& context);
3029
static emscripten::val getBackground(const apl::RootContextPtr& context);
3130
static void setBackground(const apl::RootContextPtr& context, emscripten::val background);
3231
static std::string getDataSourceContext(const apl::RootContextPtr& context);

wasm/include/wasm/mediaplayer.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ class MediaPlayer : public apl::MediaPlayer {
3737
void previous() override;
3838
void rewind() override;
3939
void seek(int offset) override;
40+
void seekTo(int position) override;
4041
void setTrackIndex(int trackIndex) override;
4142
void setAudioTrack(apl::AudioTrack audioTrack) override;
4243
void setMute(bool mute) override;

wasm/src/context.cpp

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -33,15 +33,6 @@ ContextMethods::topComponent(const apl::RootContextPtr& context) {
3333
return top;
3434
}
3535

36-
std::string
37-
ContextMethods::getTheme(const apl::RootContextPtr& context) {
38-
std::string theme = "";
39-
if (context) {
40-
theme = context->getTheme();
41-
}
42-
return theme;
43-
}
44-
4536
emscripten::val
4637
ContextMethods::getBackground(const apl::RootContextPtr& context) {
4738
return background;
@@ -504,7 +495,6 @@ EMSCRIPTEN_BINDINGS(apl_wasm_context) {
504495
emscripten::class_<apl::RootContext>("Context")
505496
.smart_ptr<apl::RootContextPtr>("ContextPtr")
506497
.function("topComponent", &internal::ContextMethods::topComponent)
507-
.function("getTheme", &internal::ContextMethods::getTheme)
508498
.function("getBackground", &internal::ContextMethods::getBackground)
509499
.function("setBackground", &internal::ContextMethods::setBackground)
510500
.function("getDataSourceContext", &internal::ContextMethods::getDataSourceContext)

wasm/src/mediaplayer.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,15 @@ MediaPlayer::seek(int offset)
167167
mPlayer.call<void>("seek", offset);
168168
}
169169

170+
void
171+
MediaPlayer::seekTo(int position)
172+
{
173+
if (!isActive()) return;
174+
resolveExistingAction();
175+
176+
mPlayer.call<void>("seekTo", position);
177+
}
178+
170179
void
171180
MediaPlayer::setTrackIndex(int trackIndex)
172181
{

0 commit comments

Comments
 (0)