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

Lines changed: 94 additions & 63 deletions

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

0 commit comments

Comments
 (0)