Amphtml: I2I: Programmatic control of the amp-story-player

Created on 14 May 2020  路  10Comments  路  Source: ampproject/amphtml

Users have raised requests to control the player programmatically through an API. The proposed API design is as following:

Exposing the AmpStoryPlayer class

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>
    );
}

Initialize the player manually

  • Method signature: 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.

Player is ready for interaction

  • Custom event: ready
  • Sync property: 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!');
}

Player is focused

  • Custom event: focusin

e.g.

player.addEventListener('focusin', () => {
  console.log('Player is focused');
})

Player is focused

  • Custom event: focusout

e.g.

player.addEventListener('focusout', () => {
  console.log('User clicked away from the player');
})

Player changed story

  • Custom event: navigation
  • Return value: {index: number, remaining: number}

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
})

User reached end of story

  • Custom event: storyEnd

e.g.

player.addEventListener('storyEnd', () => {
  console.log('User is in the last page of the story');
})

Adding stories to the player programmatically

  • Method signature: player.add(storiesArray)
  • Note that the player will try to load the story from the cache that was specified at the player level. e.g. cdn.ampproject.org / www.bing-amp.com

Parameters:

  • storiesArray: (Array) Array containing objects of type StoryObj
StoryObj type
  • url: (string) URL of the story.
  • opt_title: optional (string) Title of the story to be added to the anchor's title.

Change the current story being displayed by the player

  • Method signature: player.show(storyUrl, animate)
  • If story hasn't been previously added, add() will be called internally before.

Parameters:

  • storyUrl: (string) URL of the story to show.
  • animate: (boolean) Whether the story should animate in or not. Defaults to false.

Navigate between stories in the player

  • Method signature: player.go(storyDelta, opt_pageDelta)

Parameters:

  • storyDelta: (number) The story in the player to which you want to move, relative to the current story. A negative value moves backwards, a positive value moves forwards. So, for example, player.go(2) moves forward two stories and player.go(-2) moves back two stories. If no value is passed or if delta equals 0, current story will persist and no action will be taken.
  • pageDelta: (number) The page in the story to which you want to move, relative to the current page. A negative value moves backwards, a positive value moves forwards. So, for example, player.go(0, 1) moves forward one page in the current story and player.go(1, -2) moves back two pages in the next story. If no value is passed or if delta equals 0, current page will persist and no action will be taken.

Pause current story in the player

  • Method signature: player.pause()

Play current story in the player

  • Method signature: player.play()

Mute the content of the player

  • Method signature: player.mute()

Unmute the content of the player

  • Method signature: player.unmute()

Examples

Full example using APIs

// 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

INTENT TO IMPLEMENT High Priority stories

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)

All 10 comments

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.
  • It could be cool to have a way to reorder the stories inside the player. A use case could to put unseen stories first.
  • An event for when the story lose focus would be nice too.

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:

Was this page helpful?
0 / 5 - 0 ratings