Users have raised requests to control the player programmatically through an API. The proposed API design is as following:
In order for other libraries to control the player and its behavior (e.g. when it loads) we plan on exposing the AmpStoryPlayer
class as a global variable.
This will be exposed in src/amp-story-player/amp-story-player.js
:
globalThis.AmpStoryPlayer = AmpStoryPlayer;
This will enable users using other frameworks (e.g. React) to do something like this:
function StoryPlayer({ url, title, poster, width = 360, height = 600 }) {
useEffect(() => {
const playerEl = document.body.querySelector('amp-story-player');
const player = new AmpStoryPlayer(window, playerEl); // Previously exposed global var
player.load();
}, []);
return (
<div>
<amp-story-player style={{ width: `${width}px`, height: `${height}px`}}>
<a href={url}>
{title}
</a>
</amp-story-player>
</div>
);
}
player.load()
This can be useful in situations where the HTML of the player is dynamically appended. For example, when using React and its render() method.
ready
player.isReady
The sync property can be used to avoid race conditions, e.g. when starting to listen to the custom event after it has been dispatched.
e.g.
player.addEventListener('ready', () => {
console.log('Player is ready!!');
})
if (player.isReady) {
console.log('Player is ready!');
}
focusin
e.g.
player.addEventListener('focusin', () => {
console.log('Player is focused');
})
focusout
e.g.
player.addEventListener('focusout', () => {
console.log('User clicked away from the player');
})
navigation
e.g.
player.addEventListener('navigation', (event) => {
console.log('Navigated from story 0 to story 1 of 3');
console.log('Current story:' event.index); // 1
console.log('Current story:' event.remaining); // 1
})
storyEnd
e.g.
player.addEventListener('storyEnd', () => {
console.log('User is in the last page of the story');
})
player.add(storiesArray)
cdn.ampproject.org
/ www.bing-amp.com
StoryObj
title
.player.show(storyUrl, animate)
add()
will be called internally before.false
.player.go(storyDelta, opt_pageDelta)
player.pause()
player.play()
player.mute()
player.unmute()
// HTML
<head>
<script
async
src="https://cdn.ampproject.org/amp-story-player-v0.js"
onload="initializePlayer()"
></script>
<link
href="https://cdn.ampproject.org/amp-story-player-v0.css"
rel="stylesheet"
type="text/css"
/>
</head>
<body>
<button onclick="nextStory()">Next story</button>
<button onclick="player.pause()">Pause story</button>
<button onclick="show('e.com/first')">First story</button>
<button onclick="show('e.com/second')">Second story</button>
<button onclick="show('e.com/third')">Third story</button>
<amp-story-player style="width: 360px; height: 600px;">
<a href="https://e.com/first" style="--story-player-poster: url('https://example.dev/image.jpg')">
First story
</a>
<a href="https://e.com/second">
Second story
</a>
<a href="https://e.com/third">
Third story
</a>
</amp-story-player>
</body>
// JS
let player;
function initializePlayer() {
player = window.document.querySelector('amp-story-player');
if (player.isReady) {
// Player loaded. Run e.g. animation.
return;
}
player.addEventListener('ready', () => {
// Player loaded. Run e.g. animation.
})
}
function show(story) {
player.show(story)
}
function nextStory() {
player.go(1);
}
/cc @ampproject/wg-stories
Hi,
I've got a feedback on the subject :
Will there be a security to prevent unmute without user interaction. It would be cool if it was possible to unmute a story onFocused for example.
Yeah, I think this should be possible.
It could be cool to have a way to reorder the stories inside the player. A use case could to put unseen stories first.
That is a very cool idea, we should discuss what's the right level of control that the player should provide here, since there are several ways to accomplish this. e.g. having the equivalent of a push()
and pop()
methods. The publisher can then keep track of what stories has the user seen and update them as necessary.
Another approach is for the player to keep track of which stories have been played (specified by a config option) and push those to the end. But since this is purely client-side, the state would be lost once the user leaves the page or refreshes.
Maybe listening to the navigation
event and keeping track of the indices of the stories that have been played, plus the addition of a player.pushToBack(index)
method would suffice?
An event for when the story lose focus would be nice too.
Good point. I updated to have both focusin
and focusout
.
Awesome Enrique! This LGTM :))
this would help me with some prototyping & testing tools :)
(specifically, it would be nice to be able to request navigation forward and backwards)
@Enriqe WDYT about renaming go(...)
to goToStory(...)
and then also having an analagous goToPage(...)
?
That'd be a cool feature!
What's good about go()
over goToStory()
is that the first one accepts an index, while goToStory
sounds like it would accept a story ID. Same for goToPage()
.
Wdyt about having the go()
method accept two parameters: go(storyDelta, pageDelta)
?
go(1)
goes to the next story
go(0, 1)
goes to the next page
I like the idea of extending the go()
method to support a second parameter with the pageDelta
. What's the use case you had in mind @newmuis?
my use-case is wanting to have multiple players and control their advancements individually, for testing & quality assurance of new stories
Got it, that makes sense. Thanks for the context @MaxBittker , I'll add it to the spec
FRs from @cramforce:
Most helpful comment
this would help me with some prototyping & testing tools :)
(specifically, it would be nice to be able to request navigation forward and backwards)