forked from ampproject/amphtml
-
Notifications
You must be signed in to change notification settings - Fork 0
/
video-interface.js
608 lines (547 loc) · 14.6 KB
/
video-interface.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
import {devAssertElement} from '#core/assert';
import {whenUpgradedToCustomElement} from '#core/dom/amp-element-helpers';
export const MIN_VISIBILITY_RATIO_FOR_AUTOPLAY = 0.5;
/**
* VideoInterface defines a common video API which any AMP component that plays
* videos is expected to implement.
*
* AMP runtime uses this common API to provide consistent video experience and
* analytics across all video players.
*
* Components implementing this interface must also extend
* {@link ./base-element.BaseElement} and register with the
* Video Manager {@link ./service/video-manager-impl.VideoManager} during
* their `builtCallback`.
*
* @interface
*/
export class VideoInterface {
/** @return {!AmpElement} */
get element() {}
/** @return {!Window} */
get win() {}
/**
* See `BaseElement`.
* @return {!./utils/signals.Signals}
*/
signals() {}
/**
* See `BaseElement`.
* @param {function()} unusedMutator
* @return {!Promise}
*/
mutateElementSkipRemeasure(unusedMutator) {}
/**
* Whether the component supports video playback in the current platform.
* If false, component will be not treated as a video component.
* @return {boolean}
*/
supportsPlatform() {}
/**
* Whether users can interact with the video such as pausing it.
* Example of non-interactive videos include design background videos where
* all controls are hidden from the user.
*
* @return {boolean}
*/
isInteractive() {}
/**
* Current playback time in seconds at time of trigger.
*
* This is used for analytics metadata.
*
* @return {number}
*/
getCurrentTime() {}
/**
* Total duration of the video in seconds
*
* This is used for analytics metadata.
*
* @return {number}
*/
getDuration() {}
/**
* Get a 2d array of start and stop times that the user has watched.
* This is used for analytics metadata.
* @return {!Array<Array<number>>}
*/
getPlayedRanges() {}
/**
* Plays the video.
*
* @param {boolean} unusedIsAutoplay Whether the call to the `play` method is
* triggered by the autoplay functionality. Video players can use this hint
* to make decisions such as not playing pre-roll video ads.
*/
play(unusedIsAutoplay) {}
/**
* Pauses the video.
*/
pause() {}
/**
* Mutes the video.
* Implementation is required for autoplay and mute/unmute controls on docked
* video.
*/
mute() {}
/**
* Unmutes the video.
* Implementation is required for autoplay and mute/unmute controls on docked
* video.
*/
unmute() {}
/**
* Makes the video UI controls visible.
*
* AMP will not call this method if `controls` attribute is not set.
*
* Implementation is required for docked video.
*/
showControls() {}
/**
* Hides the video UI controls.
*
* AMP will not call this method if `controls` attribute is not set.
*
* Implementation is required for docked video.
*/
hideControls() {}
/**
* Returns video's meta data (artwork, title, artist, album, etc.) for use
* with the Media Session API
* @return {!./mediasession-helper.MetadataDef|undefined} metadata
* - artwork (Array): URL to the poster image (preferably a 512x512 PNG)
* - title (string): Name of the video
* - artist (string): Name of the video's author/artist
* - album (string): Name of the video's album if it exists
*/
getMetadata() {}
/**
* If returning true, it's assumed that the embedded video document internally
* implements a feature to enter fullscreen on device rotation, so that the
* VideoManager does not override it.
*
* Otherwise, the feature is implemented automatically when using the
* `rotate-to-fullscreen` attribute.
*
* @return {boolean}
*/
preimplementsAutoFullscreen() {}
/**
* If returning true, it's assumed that the embedded video document internally
* implements the MediaSession API internally so that the VideoManager won't
* replace it.
*
* Otherwise provided and inferred metadata are used to update the video's
* Media Session.
*
* @return {boolean}
*/
preimplementsMediaSessionAPI() {}
/**
* Enables fullscreen on the internal video element
* NOTE: While implementing, keep in mind that Safari/iOS do not allow taking
* any element other than <video> to fullscreen, if the player has an internal
* implementation of fullscreen (flash for example) then check
* if Services.platformFor(this.win).isSafari is true and use the internal
* implementation instead. If not, it is recommended to take the iframe
* to fullscreen using fullscreenEnter from src/core/dom/fullscreen.js
*/
fullscreenEnter() {}
/**
* Quits fullscreen mode
*/
fullscreenExit() {}
/**
* Returns whether the video is currently in fullscreen mode or not.
* @return {boolean}
*/
isFullscreen() {}
/**
* Seeks the video to a specified time.
* @param {number} unusedTimeSeconds
*/
seekTo(unusedTimeSeconds) {}
}
/**
* Attributes
*
* Components implementing the VideoInterface are expected to support
* the following attributes.
*
* @enum {string}
*/
export const VideoAttributes_Enum = {
/**
* autoplay
*
* Whether the developer has configured autoplay on the component.
* This is normally done by setting `autoplay` attribute on the component.
*
* AMP runtime manages autoplay behavior itself using methods such as `play`,
* `pause`, `showControls`, `hideControls`, `mute`, etc.. therefore components
* should not propagate the autoplay attribute to the underlying player
* implementation.
*
* When a video is requested to autoplay, AMP will automatically
* mute and hide the controls for the video, when video is 75% visible in
* the viewport, AMP will play the video and later pauses it when 25%
* or more of the video exits the viewport. If an auto-playing video also has
* controls, AMP will install a tap
* handler on the video, and when an end-user taps the video, AMP will show
* the controls.
*
*/
AUTOPLAY: 'autoplay',
/**
* dock
*
* Setting the `dock` attribute on the component makes the video minimize
* to the corner when scrolled out of view and has been interacted with.
*/
DOCK: 'dock',
/**
* rotate-to-fullscreen
*
* If enabled, this automatically expands the currently visible video and
* playing to fullscreen when the user changes the device's orientation to
* landscape if the video was started following a user interaction
* (not autoplay)
*
* Dependent upon browser support of
* http://caniuse.com/#feat=screen-orientation
* and http://caniuse.com/#feat=fullscreen
*/
ROTATE_TO_FULLSCREEN: 'rotate-to-fullscreen',
/**
* noaudio
*
* If set and autoplay, the equalizer icon will not be displayed.
*/
NO_AUDIO: 'noaudio',
};
/**
* Events
*
* Components implementing the VideoInterface are expected to dispatch
* the following DOM events.
*
* @enum {string}
*/
export const VideoEvents_Enum = {
/**
* registered
*
* Fired when the video player element is built and has been registered with
* the video manager.
*
* @event registered
*/
REGISTERED: 'registered',
/**
* load
*
* Fired when the video player is loaded and calls to methods such as `play()`
* are allowed.
*
* @event load
*/
LOAD: 'load',
/**
* loadedmetadata
*
* Fired when the video's metadata becomes available (e.g. duration).
*
* @event loadedmetadata
*/
LOADEDMETADATA: 'loadedmetadata',
/**
* loadeddata
*
* Fired when the user agent can render the media for the first time.
*
* @event loadeddata
*/
LOADEDDATA: 'loadeddata',
/**
* play
*
* Fired when the video plays (either because of autoplay or the play method).
*
* Note: Because this event was not originally present in this interface, we
* cannot rely on all all implementations to emit it.
*
* @event play
*/
PLAY: 'play',
/**
* playing
*
* Fired when the video begins playing.
*
* @event playing
*/
PLAYING: 'playing',
/**
* pause
*
* Fired when the video pauses.
*
* @event pause
*/
PAUSE: 'pause',
/**
* ended
*
* Fired when the video ends.
*
* This event should be fired in addition to `pause` when video ends.
*
* @event ended
*/
ENDED: 'ended',
/**
* muted
*
* Fired when the video is muted.
*
* @event muted
*/
MUTED: 'muted',
/**
* unmuted
*
* Fired when the video is unmuted.
*
* @event unmuted
*/
UNMUTED: 'unmuted',
/**
* amp:video:visibility
*
* Fired when the video's visibility changes.
*
* @event amp:video:visibility
* @property {boolean} visible Whether the video player is visible or not.
*/
VISIBILITY: 'amp:video:visibility',
/**
* reload
*
* Fired when the video's src changes.
*
* @event reloaded
*/
RELOAD: 'reloaded',
/**
* pre/mid/post Ad start
*
* Fired when an Ad starts playing.
*
* This is used to remove any overlay shims during Ad play during autoplay
* or minimized-to-corner version of the player.
*
* @event ad_start
*/
AD_START: 'ad_start',
/**
* pre/mid/post Ad ends
*
* Fired when an Ad ends playing.
*
* This is used to restore any overlay shims during Ad play during autoplay
* or minimized-to-corner version of the player.
*
* @event ad_end
*/
AD_END: 'ad_end',
/**
* A 3p video player can send signals for analytics whose meaning doesn't
* fit for other events. In this case, a `tick` event is sent with additional
* information in its data property.
*
* @event amp:video:tick
*/
CUSTOM_TICK: 'amp:video:tick',
};
/** @typedef {string} */
export let PlayingStateDef;
/**
* Playing States
*
* Internal playing states used to distinguish between video playing on user's
* command and videos playing automatically
*
* @enum {string}
*/
export const PlayingStates_Enum = {
/**
* playing_manual
*
* When the video user manually interacted with the video and the video
* is now playing
*
* @event playing_manual
*/
PLAYING_MANUAL: 'playing_manual',
/**
* playing_auto
*
* When the video has autoplay and the user hasn't interacted with it yet
*
* @event playing_auto
*/
PLAYING_AUTO: 'playing_auto',
/**
* paused
*
* When the video is paused.
*
* @event paused
*/
PAUSED: 'paused',
};
/** @enum {string} */
export const VideoAnalyticsEvents_Enum = {
/**
* video-ended
*
* Indicates that a video ended.
* @property {!VideoAnalyticsDetailsDef} details
* @event video-ended
*/
ENDED: 'video-ended',
/**
* video-pause
*
* Indicates that a video paused.
* @property {!VideoAnalyticsDetailsDef} details
* @event video-pause
*/
PAUSE: 'video-pause',
/**
* video-play
*
* Indicates that a video began to play.
* @property {!VideoAnalyticsDetailsDef} details
* @event video-play
*/
PLAY: 'video-play',
/**
* video-session
*
* Indicates that some segment of the video played.
* @property {!VideoAnalyticsDetailsDef} details
* @event video-session
*/
SESSION: 'video-session',
/**
* video-session-visible
*
* Indicates that some segment of the video played in the viewport.
* @property {!VideoAnalyticsDetailsDef} details
* @event video-session-visible
*/
SESSION_VISIBLE: 'video-session-visible',
/**
* video-seconds-played
*
* Indicates that a video was playing when the
* video-seconds-played interval fired.
* @property {!VideoAnalyticsDetailsDef} details
* @event video-session-visible
*/
SECONDS_PLAYED: 'video-seconds-played',
/**
* video-hosted-custom
*
* Indicates that a custom event incoming from a 3p frame is to be logged.
* @property {!VideoAnalyticsDetailsDef} details
* @event video-custom
*/
CUSTOM: 'video-hosted-custom',
/**
* video-percentage-played
*
* Indicates that a percentage interval has been played.
* @property {!VideoAnalyticsDetailsDef} details
* @event video-custom
*/
PERCENTAGE_PLAYED: 'video-percentage-played',
/**
* video-ad-start
*
* Indicates that an ad begins to play.
* @property {!VideoAnalyticsDetailsDef} details
* @event video-ad-start
*/
AD_START: 'video-ad-start',
/**
* video-ad-end
*
* Indicates that an ad ended.
* @property {!VideoAnalyticsDetailsDef} details
* @event video-ad-end
*/
AD_END: 'video-ad-end',
};
/**
* This key can't predictably collide with custom var names as defined in
* analytics user configuration.
* @type {string}
*/
export const videoAnalyticsCustomEventTypeKey = '__amp:eventType';
/**
* Helper union type to be used internally, so that the compiler treats
* `VideoInterface` objects as `BaseElement`s, which they should be anyway.
*
* WARNING: Don't use to `register` at the Service level. Registering should
* only allow `VideoInterface` as a guarding measure.
*
* @typedef {!VideoInterface|!./base-element.BaseElement}
*/
export let VideoOrBaseElementDef;
/**
* @param {!Element} element
* @return {boolean}
*/
export function isDockable(element) {
return element.hasAttribute(VideoAttributes_Enum.DOCK);
}
/** @enum {string} */
export const VideoServiceSignals_Enum = {
USER_INTERACTED: 'user-interacted',
PLAYBACK_DELEGATED: 'playback-delegated',
};
/** @param {!AmpElement|!VideoOrBaseElementDef} video */
export function delegateAutoplay(video) {
whenUpgradedToCustomElement(devAssertElement(video)).then((el) => {
el.signals().signal(VideoServiceSignals_Enum.PLAYBACK_DELEGATED);
});
}
/** @param {!AmpElement|!VideoOrBaseElementDef} video */
export function userInteractedWith(video) {
video.signals().signal(VideoServiceSignals_Enum.USER_INTERACTED);
}
/**
* Classname that media components should annotate themselves with.
* This applies to all video and audio playback components, regardless of
* whether they implement a common interface or not.
*
* TODO(go.amp.dev/issue/26984): This isn't exclusive to video, but there's no
* better place to put this now due to OWNERShip. Move.
*/
export const MEDIA_COMPONENT_CLASSNAME = 'i-amphtml-media-component';
/**
* Annotates media component element with a common classname.
* This applies to all video and audio playback components, regardless of
* whether they implement a common interface or not.
* @param {!Element} element
*
* TODO(go.amp.dev/issue/26984): This isn't exclusive to video, but there's no
* better place to put this now due to OWNERShip. Move.
*/
export function setIsMediaComponent(element) {
element.classList.add(MEDIA_COMPONENT_CLASSNAME);
}