WP VR – 360 Panorama and virtual tour creator for WordPress - Version 2.1.0

Version Description

  • Save draft fixed.
  • Error handling fixed.
  • Video support added.
  • Slider revolution conflict fix.
  • Auto rotation
  • Rotation pause and stop control

=

Download this release

Release Info

Developer rextheme
Plugin Icon 128x128 WP VR – 360 Panorama and virtual tour creator for WordPress
Version 2.1.0
Comparing to
See all releases

Code changes from version 2.0.0 to 2.1.0

README.txt CHANGED
@@ -1,24 +1,28 @@
1
  === WP VR - 360 Panorama and virtual tour creator for WordPress ===
2
  Contributors: rextheme, coderexco
3
  Donate link: https://rextheme.com/wp-vr-360-panorama-and-virtual-tour-creator-for-wordpress/
4
- Tags: virtual tour, real estate, panorama, panorama viewer, virtual tour, 360 panorama, interactive tour
5
  Requires at least: 4.0
6
  Tested up to: 5.0
7
- Stable tag: 2.0.0
8
  Requires PHP: 5.6
9
  License: GPLv2 or later
10
  License URI: http://www.gnu.org/licenses/gpl-2.0.html
11
 
12
- Let customers take a virtual tour and get them excited!! Let them experience the glimpse of a live tour of your place.
13
 
14
  == Description ==
15
- With the WPVR, set up a virtual tour for your customer where they can navigate a 360 view of your location, switch between several spots (or rooms), get information on items on the location, zoom in and out to get better view, and get an overall idea about how your place may look in reality.
16
 
17
- > Gutenberg block is available now. Check how to use **[wpvr block](https://rextheme.com/docs/wpvr-360-panorama-and-virtual-tour-creator-for-wordpress/gutenberg-block/)** 
18
- > Get your customers hooked by giving them a tour that makes them realize how amazing your place is. Check for **[Documentation](https://rextheme.com/docs/wpvr-360-panorama-and-virtual-tour-creator-for-wordpress/)** 
 
 
 
 
19
 
20
  **Easy and simple interface** 
21
- Simple, easy and straight forward options to add your images and create a tour.
22
 
23
  **Easy direction and navigation tools**
24
  Create multiple hotspots for each scene at your own will. You can add hotspot type: info to get on-click or hover information or add URL. You can use hotspot type: scene and connect it to visit other scenes.
@@ -27,7 +31,9 @@ Create multiple hotspots for each scene at your own will. You can add hotspot ty
27
  You can implement your own custom style class and, alter the design and display of content from hotspot easily.
28
 
29
  **Easy to embed your virtual tour:**
30
- Creating every tour location generates a shortcode. You can simply apply this shorcode on your page along with proper width and height, and your website will include the virtual tour just the way you want.
 
 
31
 
32
 
33
  = Features: =
@@ -40,10 +46,14 @@ Creating every tour location generates a shortcode. You can simply apply this sh
40
  * Supports Equiretangular Panaromic Image type
41
  * Extensive keyboard and move navigation: Use direction keys or click and drag to navigate
42
  * Mouse scroll or keyboard buttons to zoom in and out
43
- * Generates shortcodes to embed item on webpage
 
44
  * Supports full screen
45
  * Support from support forum
46
  * Gutenberg block support
 
 
 
47
 
48
  = Premium Version =
49
  * Unlimited scenes
@@ -56,14 +66,12 @@ Creating every tour location generates a shortcode. You can simply apply this sh
56
  - Target scene angle of view.
57
  - Target scene default zoom level.
58
  - Horizontal and vertical grab control.
59
- - Auto rotation
60
- - Rotational angle (in seconds)
61
  - On screen control button.
62
 
63
 
64
  == Frequently Asked Questions ==
65
  = 1. Why should I use WPVR? =
66
- You can easily create a Virtual Tour for your place using WPVR. You simply need to provide a 360 degree panoramic photo, and this plugin will create a virtual tour which customers can navigate easily. You can further add hotspots to include information or add more scenes to navigate to.
67
 
68
  = 2. Installation =
69
  You can download the plugin either from wordpress.org or from rextheme.com. Once you have downloaded the file, you can then go to your dashboard, under plugin, select Add New and upload the file. Then Install and activate the plugin. Once activated, on the left side under your dashboard, you will find the option WPVR at the bottom.
@@ -77,6 +85,9 @@ Yes, you can include image source or video embed link on the section On Click Co
77
  = 5. Can I Customize the Content on the hotspot? =
78
  Yes. You can create a custom class on your theme, stating whatever style you want. Then you can input the class name on the Hotspot Custom Class section in a hotspot, and the content displayed will be customized according to the style you set.
79
 
 
 
 
80
 
81
  **[Documentation](https://rextheme.com/docs/wpvr-360-panorama-and-virtual-tour-creator-for-wordpress/)** 
82
 
@@ -103,5 +114,13 @@ Yes. You can create a custom class on your theme, stating whatever style you wan
103
  * Gutenberg block support
104
  * Bug fix.
105
 
 
 
 
 
 
 
 
 
106
  == Upgrade Notice ==
107
  Please do update the WP VR to the latest version. Each update makes it sure your plugin is supporting all tour features.  
1
  === WP VR - 360 Panorama and virtual tour creator for WordPress ===
2
  Contributors: rextheme, coderexco
3
  Donate link: https://rextheme.com/wp-vr-360-panorama-and-virtual-tour-creator-for-wordpress/
4
+ Tags: virtual tour, real estate tour, panorama, panorama viewer, virtual tour, 360 panorama, interactive tour
5
  Requires at least: 4.0
6
  Tested up to: 5.0
7
+ Stable tag: 2.1.0
8
  Requires PHP: 5.6
9
  License: GPLv2 or later
10
  License URI: http://www.gnu.org/licenses/gpl-2.0.html
11
 
12
+ Let site visitors take a virtual tour and get them excited!! Let them experience the glimpse of a live tour of your place.
13
 
14
  == Description ==
15
+ With the WPVR, set up a virtual tour for site visitors where they can navigate a 360 view of your location, switch between several spots (or rooms), get information on items on the location, zoom in and out to get better view, and get an overall idea about how your place may look in reality.
16
 
17
+ > Complete Gutenberg block support available. Check how to use **[wpvr block](https://rextheme.com/docs/wp-vr/gutenberg-block/)** 
18
+
19
+ > 360 Video support added. Check how to add **[video](https://rextheme.com/docs/wp-vr/360-degree-video-support/)** 
20
+ > Auto rotation feature added. 
21
+
22
+ > Get your site visitors hooked by giving them a tour that makes them realize how amazing your place is. Check **[Documentation](https://rextheme.com/docs/wp-vr/)** 
23
 
24
  **Easy and simple interface** 
25
+ Simple, easy and straight forward options to add your panorama images and create a tour.
26
 
27
  **Easy direction and navigation tools**
28
  Create multiple hotspots for each scene at your own will. You can add hotspot type: info to get on-click or hover information or add URL. You can use hotspot type: scene and connect it to visit other scenes.
31
  You can implement your own custom style class and, alter the design and display of content from hotspot easily.
32
 
33
  **Easy to embed your virtual tour:**
34
+ Every tour you create generates a shortcode.
35
+ For a classic editor, you can simply apply this shorcode on your page along with proper width and height, and your website will include the virtual tour just the way you want.
36
+ For Gutenberg block editor, you will find a block type “WPVR” under the common blocks. Create a WPVR block and on the toolbar, input the ID from the short code and assign your desired height and width, and the virtual tour will be embedded on your web page.
37
 
38
 
39
  = Features: =
46
  * Supports Equiretangular Panaromic Image type
47
  * Extensive keyboard and move navigation: Use direction keys or click and drag to navigate
48
  * Mouse scroll or keyboard buttons to zoom in and out
49
+ * Generates shortcodes
50
+ * For Classic editor, apply short code directly on webpage to embed item
51
  * Supports full screen
52
  * Support from support forum
53
  * Gutenberg block support
54
+ * 360 video support
55
+ * Auto rotation
56
+ * Rotation pause and stop control
57
 
58
  = Premium Version =
59
  * Unlimited scenes
66
  - Target scene angle of view.
67
  - Target scene default zoom level.
68
  - Horizontal and vertical grab control.
 
 
69
  - On screen control button.
70
 
71
 
72
  == Frequently Asked Questions ==
73
  = 1. Why should I use WPVR? =
74
+ You can easily create a Virtual Tour for your place using WPVR. You simply need to provide a 360 degree panoramic photo, and this plugin will create a virtual tour which visitors can navigate easily. You can further add hotspots to include information or add more scenes to navigate to.
75
 
76
  = 2. Installation =
77
  You can download the plugin either from wordpress.org or from rextheme.com. Once you have downloaded the file, you can then go to your dashboard, under plugin, select Add New and upload the file. Then Install and activate the plugin. Once activated, on the left side under your dashboard, you will find the option WPVR at the bottom.
85
  = 5. Can I Customize the Content on the hotspot? =
86
  Yes. You can create a custom class on your theme, stating whatever style you want. Then you can input the class name on the Hotspot Custom Class section in a hotspot, and the content displayed will be customized according to the style you set.
87
 
88
+ = 6. How can I embed a virtual tour using Gutenberg Block Editor?? =
89
+ Under common blocks, you will find a block called WPVR. Add WPVR block. Select the block and on the dynamic toolbar on the right, you will get the options to add ID, Height and Width. Collect the ID from the shorcode generated when you published a tour. Assign height and width according to your convenience.
90
+
91
 
92
  **[Documentation](https://rextheme.com/docs/wpvr-360-panorama-and-virtual-tour-creator-for-wordpress/)** 
93
 
114
  * Gutenberg block support
115
  * Bug fix.
116
 
117
+ = 2.1.0 =
118
+ * Save draft fixed.
119
+ * Error handling fixed.
120
+ * Video support added.
121
+ * Slider revolution conflict fix.
122
+ * Auto rotation
123
+ * Rotation pause and stop control
124
+
125
  == Upgrade Notice ==
126
  Please do update the WP VR to the latest version. Each update makes it sure your plugin is supporting all tour features.  
admin/class-wpvr-admin.php CHANGED
@@ -88,6 +88,7 @@ class Wpvr_Admin {
88
  wp_enqueue_style( 'materialize-css', plugin_dir_url( __FILE__ ) . 'css/materialize.min.css', array(), $this->version, 'all' );
89
  }
90
  wp_enqueue_style('panellium-css', plugin_dir_url( __FILE__ ) . 'lib/pannellum/src/css/pannellum.css', array(), true);
 
91
  wp_enqueue_style( $this->plugin_name, plugin_dir_url( __FILE__ ) . 'css/wpvr-admin.css', array(), $this->version, 'all' );
92
 
93
  }
@@ -110,11 +111,17 @@ class Wpvr_Admin {
110
  * between the defined hooks and the functions defined in this
111
  * class.
112
  */
 
113
  wp_enqueue_media();
114
- wp_enqueue_script('axios', 'https://unpkg.com/axios/dist/axios.min.js', array(), true);
115
  wp_enqueue_script('panellium-js', plugin_dir_url( __FILE__ ) . 'lib/pannellum/src/js/pannellum.js', array(), true);
116
  wp_enqueue_script('panelliumlib-js', plugin_dir_url( __FILE__ ) . 'lib/pannellum/src/js/libpannellum.js', array(), true);
117
- wp_enqueue_script( 'materialize-js', plugin_dir_url( __FILE__ ) . 'js/materialize.min.js', array( 'jquery' ), $this->version, false );
 
 
 
 
 
 
118
  wp_enqueue_script( 'jquery-repeater', plugin_dir_url( __FILE__ ) .'js/jquery.repeater.min.js', array('jquery'), true);
119
  wp_enqueue_script( $this->plugin_name, plugin_dir_url( __FILE__ ) . 'js/wpvr-admin.js', array( 'jquery' ), $this->version, false );
120
  wp_localize_script( $this->plugin_name, 'wpvr_obj', array(
@@ -268,13 +275,16 @@ class Wpvr_Admin {
268
  else {
269
  $data_limit = 5;
270
  }
 
271
  $postdata = get_post_meta( $post->ID, 'panodata', true );
 
 
272
  $autoload = true;
273
  if (isset($postdata["autoLoad"])) {
274
  $autoload = $postdata["autoLoad"];
275
  }
276
 
277
- $control = false;
278
  if (isset($postdata["showControls"])) {
279
  $control = $postdata["showControls"];
280
  }
@@ -283,6 +293,24 @@ class Wpvr_Admin {
283
  if (isset($postdata["defaultscene"])) {
284
  $default_scene = $postdata["defaultscene"];
285
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
286
 
287
  $scene_fade_duration = '';
288
  if (isset($postdata["scenefadeduration"])) {
@@ -309,6 +337,7 @@ class Wpvr_Admin {
309
  $html .='<li class="general active"><span data-href="#general"><i class="fa fa-cogs"></i> '.__('general','wpvr').'</span></li>';
310
  $html .='<li class="scene"><span data-href="#scenes"><i class="fa fa-photo"></i> '.__('Scenes','wpvr').'</span></li>';
311
  $html .='<li class="hotspot"><span data-href="#scenes"><i class="fa fa-dot-circle-o"></i> '.__('hotspot','wpvr').'</span></li>';
 
312
  $html .='</ul>';
313
  $html .='</nav>';
314
 
@@ -316,6 +345,7 @@ class Wpvr_Admin {
316
  $html .='<div class="rex-pano-tab general active" id="general">';
317
 
318
  $html .= '<h6 class="title"> '.__('General Settings : ','wpvr').'</h6>';
 
319
  //=Autoload setup=
320
  if ($autoload == true) {
321
  $html .= '<div class="single-settings autoload">';
@@ -352,17 +382,17 @@ class Wpvr_Admin {
352
  //=Autoload setup End=
353
 
354
  //=Control Setup=
355
- if ($control == true) {
356
  $html .= '<div class="single-settings controls">';
357
  $html .= '<span>'.__('Show Controls: ','wpvr').'</span>';
358
  $html .= '<ul>';
359
  $html .= '<li class="radio-btn">';
360
- $html .= '<input class="styled-radio" id="styled-radio-3" type="radio" name="controls" value="off">';
361
  $html .= '<label for="styled-radio-3">Off</label>';
362
  $html .= '</li>';
363
 
364
  $html .= '<li class="radio-btn">';
365
- $html .= '<input class="styled-radio" id="styled-radio-4" type="radio" name="controls" value="on" checked >';
366
  $html .= '<label for="styled-radio-4">On</label>';
367
  $html .= '</li>';
368
  $html .= '</ul>';
@@ -373,25 +403,86 @@ class Wpvr_Admin {
373
  $html .= '<span>'.__('Show Controls: ','wpvr').'</span>';
374
  $html .= '<ul>';
375
  $html .= '<li class="radio-btn">';
376
- $html .= '<input class="styled-radio" id="styled-radio-3" type="radio" name="controls" value="off" checked >';
377
  $html .= '<label for="styled-radio-3">Off</label>';
378
  $html .= '</li>';
379
 
380
  $html .= '<li class="radio-btn">';
381
- $html .= '<input class="styled-radio" id="styled-radio-4" type="radio" name="controls" value="on">';
382
  $html .= '<label for="styled-radio-4">On</label>';
383
  $html .= '</li>';
384
  $html .= '</ul>';
385
  $html .= '</div>';
386
  }
387
- //=Control setup End=
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
388
 
389
- //=scene fade duration=
390
  $html .= '<div class="single-settings scene-fade-duration">';
391
  $html .= '<span>'.__('Scene Fade Duration: ','wpvr').'</span>';
392
  $html .= '<input type="number" name="scene-fade-duration" value="'.$scene_fade_duration.'" />';
 
 
 
 
393
  $html .= '</div>';
394
- //=scene fade duration End=
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
395
 
396
  $html .='</div>';
397
  //---end general tab----
@@ -400,20 +491,137 @@ class Wpvr_Admin {
400
 
401
  //=Scene and Hotspot repeater=//
402
  if (empty($pano_data)) {
403
- $html .= '<div class="scene-setup rex-pano-sub-tabs" data-limit="'.$data_limit.'">';
404
 
405
  $html .= '<nav class="rex-pano-tab-nav rex-pano-nav-menu scene-nav">';
406
  $html .= '<ul>';
407
- $html .= '<li class="active"><span data-index="1" data-href="#scene-1">Scene 1</span></li>';
408
- $html .= '<li class="add" data-repeater-create><span><i class="fa fa-plus-circle"></i> Add</span></li>';
409
  $html .= '</ul>';
410
  $html .= '</nav>';
411
 
412
  $html .= '<div data-repeater-list="scene-list" class="rex-pano-tab-content">';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
413
  $html .= '<div data-repeater-item class="single-scene rex-pano-tab active" data-title="1" id="scene-1">';
414
 
415
  $html .= '<div class="scene-content">';
416
- $html .= '<h6 class="title"><i class="fa fa-cog"></i> Scene <span class="scene-num">1</span> Setting </h6>';
417
 
418
  //==Set Default Scene==//
419
  $html .= '<div class="single-settings dscene">';
@@ -427,7 +635,7 @@ class Wpvr_Admin {
427
 
428
  $html .= '<div class=scene-setting>';
429
  $html .= '<label for="scene-id">'.__('Scene ID : ','wpvr').'</label>';
430
- $html .= '<input type="text" name="scene-id"/>';
431
  $html .= '</div>';
432
 
433
  $html .= '<div class=scene-setting>';
@@ -450,15 +658,15 @@ class Wpvr_Admin {
450
 
451
  $html .= '<nav class="rex-pano-tab-nav rex-pano-nav-menu hotspot-nav">';
452
  $html .= '<ul>';
453
- $html .= '<li class="active"><span data-index="1" data-href="#scene-1-hotspot-1">Hotspot 1</span></li>';
454
- $html .= '<li class="add" data-repeater-create><span><i class="fa fa-plus-circle"></i> Add</span></li>';
455
  $html .= '</ul>';
456
  $html .= '</nav>';
457
 
458
  $html .= '<div data-repeater-list="hotspot-list" class="rex-pano-tab-content">';
459
  $html .= '<div data-repeater-item class="single-hotspot rex-pano-tab active clearfix" id="scene-1-hotspot-1">';
460
 
461
- $html .= '<h6 class="title"><i class="fa fa-cog"></i> Hotspot <span class="hotspot-num">1</span> Setting for <span>Scene <span class="scene-num">1</span></span> </h6>';
462
 
463
  $html .= '<div class="wrapper">';
464
  $html .= '<div class="hotspot-setting">';
@@ -468,12 +676,12 @@ class Wpvr_Admin {
468
 
469
  $html .= '<div class="hotspot-setting">';
470
  $html .= '<label for="hotspot-pitch">'.__('Pitch: ','wpvr').'</label>';
471
- $html .= '<input type="text" id="hotspot-pitch" name="hotspot-pitch"/>';
472
  $html .= '</div>';
473
 
474
  $html .= '<div class="hotspot-setting">';
475
  $html .= '<label for="hotspot-yaw">'.__('Yaw: ','wpvr').'</label>';
476
- $html .= '<input type="text" id="hotspot-yaw" name="hotspot-yaw"/>';
477
  $html .= '</div>';
478
 
479
  $html .= '<div class="hotspot-setting">';
@@ -504,9 +712,15 @@ class Wpvr_Admin {
504
  $html .= '<textarea name="hotspot-hover"></textarea>';
505
  $html .= '</div>';
506
 
 
 
 
 
 
 
507
  $html .= '<div class="hotspot-scene" style="display:none;" >';
508
  $html .= '<label for="hotspot-scene">'.__('Target Scene ID: ','wpvr').'</label>';
509
- $html .= '<input type="text" name="hotspot-scene"/>';
510
  $html .= '</div>';
511
 
512
  $html .= '</div>';
@@ -523,22 +737,23 @@ class Wpvr_Admin {
523
  $html .= '</div>';
524
  }
525
  else {
526
- $html .= '<div class="scene-setup rex-pano-sub-tabs" data-limit="'.$data_limit.'">';
527
 
528
  $html .= '<nav class="rex-pano-tab-nav rex-pano-nav-menu scene-nav">';
529
  $html .= '<ul>';
530
- $html .= '<li><span data-index="0" data-href="#scene-0">Scene 0</span></li>';
531
  $i = 1;
 
532
  foreach ($pano_data["scene-list"] as $pano_scenes) {
533
- if ($pano_scenes['scene-id'] == $pano_data['scene-list'][0]['scene-id']) {
534
- $html .= '<li class="active"><span data-index="'.$i.'" data-href="#scene-'.$i.'">Scene '.$i.'</span></li>';
535
  }
536
  else {
537
- $html .= '<li><span data-index="'.$i.'" data-href="#scene-'.$i.'">Scene '.$i.'</span></li>';
538
  }
539
  $i++;
540
  }
541
- $html .= '<li class="add" data-repeater-create><span><i class="fa fa-plus-circle"></i> Add</span></li>';
542
  $html .= '</ul>';
543
  $html .= '</nav>';
544
 
@@ -549,7 +764,7 @@ class Wpvr_Admin {
549
  $html .= '<div data-repeater-item class="single-scene rex-pano-tab" data-title="0" id="scene-0">';
550
 
551
  $html .= '<div class="scene-content">';
552
- $html .= '<h6 class="title"><i class="fa fa-cog"></i> Scene <span class="scene-num">0</span> Setting </h6>';
553
 
554
  //==Set Default Scene==//
555
  $html .= '<div class="single-settings dscene">';
@@ -562,7 +777,7 @@ class Wpvr_Admin {
562
  //==Set Default Scene end==//
563
  $html .= '<div class=scene-setting>';
564
  $html .= '<label for="scene-id">'.__('Scene ID : ','wpvr').'</label>';
565
- $html .= '<input type="text" name="scene-id"/>';
566
  $html .= '</div>';
567
 
568
  $html .= '<div class=scene-setting>';
@@ -585,15 +800,15 @@ class Wpvr_Admin {
585
 
586
  $html .= '<nav class="rex-pano-tab-nav rex-pano-nav-menu hotspot-nav">';
587
  $html .= '<ul>';
588
- $html .= '<li class="active"><span data-index="1" data-href="#scene-0-hotspot-1">Hotspot 1</span></li>';
589
- $html .= '<li class="add" data-repeater-create><span><i class="fa fa-plus-circle"></i> Add</span></li>';
590
  $html .= '</ul>';
591
  $html .= '</nav>';
592
 
593
  $html .= '<div data-repeater-list="hotspot-list" class="rex-pano-tab-content">';
594
  $html .= '<div data-repeater-item class="single-hotspot rex-pano-tab active clearfix" id="scene-0-hotspot-1">';
595
 
596
- $html .= '<h6 class="title"><i class="fa fa-cog"></i> Hotspot <span class="hotspot-num">1</span> Setting for <span>Scene <span class="scene-num">0</span></span> </h6>';
597
 
598
  $html .= '<div class="wrapper">';
599
  $html .= '<div class="hotspot-setting">';
@@ -602,13 +817,13 @@ class Wpvr_Admin {
602
  $html .= '</div>';
603
 
604
  $html .= '<div class="hotspot-setting">';
605
- $html .= '<label for="hotspot-pitch">'.__('Pitch: ','wpvr').' </label>';
606
- $html .= '<input type="text" id="hotspot-pitch" name="hotspot-pitch"/>';
607
  $html .= '</div>';
608
 
609
  $html .= '<div class="hotspot-setting">';
610
- $html .= '<label for="hotspot-yaw">'.__('Yaw: ','wpvr').' </label>';
611
- $html .= '<input type="text" id="hotspot-yaw" name="hotspot-yaw"/>';
612
  $html .= '</div>';
613
 
614
  $html .= '<div class="hotspot-setting">';
@@ -638,10 +853,17 @@ class Wpvr_Admin {
638
  $html .= '<label for="hotspot-hover">'.__('On hover Content: ','wpvr').'</label>';
639
  $html .= '<textarea name="hotspot-hover"></textarea>';
640
  $html .= '</div>';
 
 
 
 
 
 
 
641
 
642
  $html .= '<div class="hotspot-scene" style="display:none;" >';
643
  $html .= '<label for="hotspot-scene">'.__('Target Scene ID: ','wpvr').' </label>';
644
- $html .= '<input type="text" name="hotspot-scene"/>';
645
  $html .= '</div>';
646
 
647
  $html .= '</div>';
@@ -672,12 +894,12 @@ class Wpvr_Admin {
672
  $pano_hotspots = $pano_scenes["hotspot-list"];
673
  }
674
 
675
-
676
- if ($pano_scenes['scene-id'] == $pano_data['scene-list'][0]['scene-id']) {
677
  $html .= '<div data-repeater-item class="single-scene rex-pano-tab active" data-title="1" id="scene-'.$s.'">';
678
 
679
  $html .= '<div class="scene-content">';
680
- $html .= '<h6 class="title"><i class="fa fa-cog"></i> Scene <span class="scene-num">'.$s.'</span> Setting </h6>';
681
  //==Set Default Scene==//
682
  if ($dscene == 'on') {
683
  $html .= '<div class="single-settings dscene">';
@@ -701,7 +923,7 @@ class Wpvr_Admin {
701
  //==Set Default Scene end==//
702
  $html .= '<div class=scene-setting>';
703
  $html .= '<label for="scene-id">'.__('Scene ID : ','wpvr').'</label>';
704
- $html .= '<input type="text" name="scene-id" value="'.$scene_id.'" />';
705
  $html .= '</div>';
706
 
707
  $html .= '<div class=scene-setting>';
@@ -725,22 +947,94 @@ class Wpvr_Admin {
725
  $html .= '<nav class="rex-pano-tab-nav rex-pano-nav-menu hotspot-nav">';
726
  $html .= '<ul>';
727
  $j = 1;
 
728
  foreach ($pano_hotspots as $pano_hotspot) {
729
- if ($pano_hotspot['hotspot-title'] == $pano_hotspots[0]['hotspot-title']) {
730
- $html .= '<li class="active"><span data-index="'.$j.'" data-href="#scene-'.$s.'-hotspot-'.$j.'">Hotspot '.$j.'</span></li>';
 
731
  }
732
  else {
733
- $html .= '<li><span data-index="'.$j.'" data-href="#scene-'.$s.'-hotspot-'.$j.'">Hotspot '.$j.'</span></li>';
734
  }
735
  $j++;
736
  }
737
- $html .= '<li class="add" data-repeater-create><span><i class="fa fa-plus-circle"></i> Add</span></li>';
738
  $html .= '</ul>';
739
  $html .= '</nav>';
740
 
741
  $html .= '<div data-repeater-list="hotspot-list" class="rex-pano-tab-content">';
742
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
743
  $h = 1;
 
744
  foreach ($pano_hotspots as $pano_hotspot) {
745
  $hotspot_title = '';
746
  $hotspot_title = $pano_hotspot['hotspot-title'];
@@ -760,10 +1054,10 @@ class Wpvr_Admin {
760
  $hotspot_target_scene = $pano_hotspot['hotspot-scene'];
761
  $hotspot_custom_class = '';
762
  $hotspot_custom_class = $pano_hotspot['hotspot-customclass'];
763
- if ($pano_hotspot['hotspot-title'] == $pano_hotspots[0]['hotspot-title']) {
764
  $html .= '<div data-repeater-item class="single-hotspot rex-pano-tab active clearfix" id="scene-'.$s.'-hotspot-'.$h.'">';
765
 
766
- $html .= '<h6 class="title"><i class="fa fa-cog"></i> Hotspot <span class="hotspot-num">'.$h.'</span> Setting for <span>Scene <span class="scene-num">'.$s.'</span></span> </h6>';
767
 
768
  $html .= '<div class="wrapper">';
769
  $html .= '<div class="hotspot-setting">';
@@ -772,13 +1066,14 @@ class Wpvr_Admin {
772
  $html .= '</div>';
773
 
774
  $html .= '<div class="hotspot-setting">';
775
- $html .= '<label for="hotspot-pitch">'.__('Pitch: ','wpvr').'</label>';
776
- $html .= '<input type="text" id="hotspot-pitch" name="hotspot-pitch" value="'.$hotspot_pitch.'" />';
 
777
  $html .= '</div>';
778
 
779
  $html .= '<div class="hotspot-setting">';
780
  $html .= '<label for="hotspot-yaw">'.__('Yaw: ','wpvr').'</label>';
781
- $html .= '<input type="text" id="hotspot-yaw" name="hotspot-yaw" value="'.$hotspot_yaw.'" />';
782
  $html .= '</div>';
783
 
784
  $html .= '<div class="hotspot-setting">';
@@ -812,9 +1107,16 @@ class Wpvr_Admin {
812
  $html .= '<textarea name="hotspot-hover">'.$hotspot_hover.'</textarea>';
813
  $html .= '</div>';
814
 
 
 
 
 
 
 
 
815
  $html .= '<div class="hotspot-scene" style="display:none;" >';
816
  $html .= '<label for="hotspot-scene"> '.__('Target Scene ID: ','wpvr').'</label>';
817
- $html .= '<input type="text" name="hotspot-scene"/>';
818
  $html .= '</div>';
819
 
820
  $html .= '</div>';
@@ -824,7 +1126,7 @@ class Wpvr_Admin {
824
 
825
  $html .= '<div class="hotspot-type hotspot-setting">';
826
  $html .= '<label for="hotspot-type">'.__('Hotspot-Type: ','wpvr').'</label>';
827
- $html .= '<select name="hotspot-type">';
828
  $html .= '<option value="info"> Info</option>';
829
  $html .= '<option value="scene" selected> Scene</option>';
830
  $html .= '</select>';
@@ -834,7 +1136,7 @@ class Wpvr_Admin {
834
  $html .= '<input type="url" name="hotspot-url" />';
835
  $html .= '</div>';
836
 
837
- $html .= '<div class="hotspot-content" style="display:none;>';
838
  $html .= '<label for="hotspot-content">'.__('On click Content: ','wpvr').' </label>';
839
  $html .= '<textarea name="hotspot-content"></textarea>';
840
  $html .= '</div>';
@@ -844,9 +1146,16 @@ class Wpvr_Admin {
844
  $html .= '<textarea name="hotspot-hover">'.$hotspot_hover.'</textarea>';
845
  $html .= '</div>';
846
 
 
 
 
 
 
 
 
847
  $html .= '<div class="hotspot-scene">';
848
  $html .= '<label for="hotspot-scene">'.__('Target Scene ID: ','wpvr').'</label>';
849
- $html .= '<input type="text" name="hotspot-scene" value="'.$hotspot_target_scene.'" />';
850
  $html .= '</div>';
851
 
852
  $html .= '</div>';
@@ -859,7 +1168,7 @@ class Wpvr_Admin {
859
  else {
860
  $html .= '<div data-repeater-item class="single-hotspot rex-pano-tab clearfix" id="scene-'.$s.'-hotspot-'.$h.'">';
861
 
862
- $html .= '<h6 class="title"><i class="fa fa-cog"></i> Hotspot <span class="hotspot-num">'.$h.'</span> Setting for <span>Scene <span class="scene-num">'.$s.'</span></span> </h6>';
863
 
864
  $html .= '<div class="wrapper">';
865
  $html .= '<div class="hotspot-setting">';
@@ -868,13 +1177,13 @@ class Wpvr_Admin {
868
  $html .= '</div>';
869
 
870
  $html .= '<div class="hotspot-setting">';
871
- $html .= '<label for="hotspot-pitch">'.__('Pitch: ','wpvr').' </label>';
872
- $html .= '<input type="text" id="hotspot-pitch" name="hotspot-pitch" value="'.$hotspot_pitch.'" />';
873
  $html .= '</div>';
874
 
875
  $html .= '<div class="hotspot-setting">';
876
- $html .= '<label for="hotspot-yaw">'.__('Yaw: ','wpvr').' </label>';
877
- $html .= '<input type="text" id="hotspot-yaw" name="hotspot-yaw" value="'.$hotspot_yaw.'" />';
878
  $html .= '</div>';
879
 
880
  $html .= '<div class="hotspot-setting">';
@@ -908,9 +1217,16 @@ class Wpvr_Admin {
908
  $html .= '<textarea name="hotspot-hover">'.$hotspot_hover.'</textarea>';
909
  $html .= '</div>';
910
 
 
 
 
 
 
 
 
911
  $html .= '<div class="hotspot-scene" style="display:none;" >';
912
  $html .= '<label for="hotspot-scene">'.__('Target Scene ID: ','wpvr').'</label>';
913
- $html .= '<input type="text" name="hotspot-scene"/>';
914
  $html .= '</div>';
915
 
916
  $html .= '</div>';
@@ -920,7 +1236,7 @@ class Wpvr_Admin {
920
 
921
  $html .= '<div class="hotspot-type hotspot-setting">';
922
  $html .= '<label for="hotspot-type">'.__('Hotspot-Type: ','wpvr').'</label>';
923
- $html .= '<select name="hotspot-type">';
924
  $html .= '<option value="info"> Info</option>';
925
  $html .= '<option value="scene" selected> Scene</option>';
926
  $html .= '</select>';
@@ -930,7 +1246,7 @@ class Wpvr_Admin {
930
  $html .= '<input type="url" name="hotspot-url" />';
931
  $html .= '</div>';
932
 
933
- $html .= '<div class="hotspot-content" style="display:none;>';
934
  $html .= '<label for="hotspot-content">'.__('On click Content: ','wpvr').'</label>';
935
  $html .= '<textarea name="hotspot-content"></textarea>';
936
  $html .= '</div>';
@@ -940,9 +1256,16 @@ class Wpvr_Admin {
940
  $html .= '<textarea name="hotspot-hover">'.$hotspot_hover.'</textarea>';
941
  $html .= '</div>';
942
 
 
 
 
 
 
 
 
943
  $html .= '<div class="hotspot-scene">';
944
  $html .= '<label for="hotspot-scene">'.__('Target Scene ID: ','wpvr').'</label>';
945
- $html .= '<input type="text" name="hotspot-scene" value="'.$hotspot_target_scene.'" />';
946
  $html .= '</div>';
947
 
948
  $html .= '</div>';
@@ -957,79 +1280,6 @@ class Wpvr_Admin {
957
  $html .= '</div>';
958
  $html .= '</div>';
959
  }
960
- // else {
961
- // $html .= '<div class="hotspot-setup rex-pano-sub-tabs" data-limit="'.$data_limit.'">';
962
-
963
- // $html .= '<nav class="rex-pano-tab-nav rex-pano-nav-menu hotspot-nav">';
964
- // $html .= '<ul>';
965
- // $html .= '<li class="active"><span data-index="0" data-href="#scene-'.$s.'-hotspot-1">Hotspot 1</span></li>';
966
- // $html .= '<li class="add" data-repeater-create><span><i class="fa fa-plus-circle"></i> Add</span></li>';
967
- // $html .= '</ul>';
968
- // $html .= '</nav>';
969
-
970
- // $html .= '<div data-repeater-list="hotspot-list" class="rex-pano-tab-content>';
971
-
972
- // $html .= '<div data-repeater-item class="single-hotspot rex-pano-tab active clearfix" id="scene-'.$s.'-hotspot-1">';
973
-
974
- // $html .= '<h6 class="title"><i class="fa fa-cog"></i> Hotspot <span class="hotspot-num">1</span> Setting for <span>Scene <span class="scene-num">'.$s.'</span></span> </h6>';
975
-
976
- // $html .= '<div class="wrapper">';
977
- // $html .= '<div class="hotspot-setting">';
978
- // $html .= '<label for="hotspot-title">'.__('Hotspot ID : ','wpvr').'</label>';
979
- // $html .= '<input type="text" id="hotspot-title" name="hotspot-title"/>';
980
- // $html .= '</div>';
981
-
982
- // $html .= '<div class="hotspot-setting">';
983
- // $html .= '<label for="hotspot-pitch">'.__('Pitch: ','wpvr').'</label>';
984
- // $html .= '<input type="text" id="hotspot-pitch" name="hotspot-pitch"/>';
985
- // $html .= '</div>';
986
-
987
- // $html .= '<div class="hotspot-setting">';
988
- // $html .= '<label for="hotspot-yaw">'.__('Yaw: ','wpvr').'</label>';
989
- // $html .= '<input type="text" id="hotspot-yaw" name="hotspot-yaw"/>';
990
- // $html .= '</div>';
991
-
992
- // $html .= '<div class="hotspot-setting">';
993
- // $html .= '<label for="hotspot-customclass">'.__('Hotspot custom class: ','wpvr').'</label>';
994
- // $html .= '<input type="text" id="hotspot-customclass" name="hotspot-customclass"/>';
995
- // $html .= '</div>';
996
- // $html .= '</div>';
997
-
998
- // $html .= '<div class="hotspot-type hotspot-setting">';
999
- // $html .= '<label for="hotspot-type">'.__('Hotspot-Type: ','wpvr').'</label>';
1000
- // $html .= '<select name="hotspot-type">';
1001
- // $html .= '<option value="info" selected> Info</option>';
1002
- // $html .= '<option value="scene"> Scene</option>';
1003
- // $html .= '</select>';
1004
-
1005
- // $html .= '<div class="hotspot-url">';
1006
- // $html .= '<label for="hotspot-url">'.__('URL: ','wpvr').'</label>';
1007
- // $html .= '<input type="url" name="hotspot-url" />';
1008
- // $html .= '</div>';
1009
-
1010
- // $html .= '<div class="hotspot-content">';
1011
- // $html .= '<label for="hotspot-content">'.__('On click Content: ','wpvr').'</label>';
1012
- // $html .= '<textarea name="hotspot-content"></textarea>';
1013
- // $html .= '</div>';
1014
-
1015
- // $html .= '<div class="hotspot-hover">';
1016
- // $html .= '<label for="hotspot-hover">'.__('On hover Content: ','wpvr').'</label>';
1017
- // $html .= '<textarea name="hotspot-hover"></textarea>';
1018
- // $html .= '</div>';
1019
-
1020
- // $html .= '<div class="hotspot-scene" style="display:none;" >';
1021
- // $html .= '<label for="hotspot-scene">'.__('Target Scene ID: ','wpvr').'</label>';
1022
- // $html .= '<input type="text" name="hotspot-scene"/>';
1023
- // $html .= '</div>';
1024
-
1025
- // $html .= '</div>';
1026
- // //=Hotspot type End=//
1027
- // $html .= '<button data-repeater-delete type="button" title="Delete Hotspot" class="delete-hotspot"><i class="fa fa-trash-o"></i></button>';
1028
- // $html .= '</div>';
1029
-
1030
- // $html .= '</div>';
1031
- // $html .= '</div>';
1032
- // }
1033
  $html .= '<button data-repeater-delete type="button" title="Delete Scene" class="delete-scene"><i class="fa fa-trash-o"></i></button>';
1034
  $html .= '</div>';
1035
  }
@@ -1037,7 +1287,7 @@ class Wpvr_Admin {
1037
  $html .= '<div data-repeater-item class="single-scene rex-pano-tab" data-title="1" id="scene-'.$s.'">';
1038
 
1039
  $html .= '<div class="scene-content">';
1040
- $html .= '<h6 class="title"><i class="fa fa-cog"></i> Scene <span class="scene-num">'.$s.'</span> Setting </h6>';
1041
 
1042
  //==Set Default Scene==//
1043
  if ($dscene == 'on') {
@@ -1063,7 +1313,7 @@ class Wpvr_Admin {
1063
 
1064
  $html .= '<div class=scene-setting>';
1065
  $html .= '<label for="scene-id">'.__('Scene ID : ','wpvr').'</label>';
1066
- $html .= '<input type="text" name="scene-id" value="'.$scene_id.'" />';
1067
  $html .= '</div>';
1068
 
1069
  $html .= '<div class=scene-setting>';
@@ -1089,14 +1339,14 @@ class Wpvr_Admin {
1089
  $j = 1;
1090
  foreach ($pano_hotspots as $pano_hotspot) {
1091
  if ($pano_hotspot['hotspot-title'] == $pano_hotspots[0]['hotspot-title']) {
1092
- $html .= '<li class="active"><span data-index="'.$j.'" data-href="#scene-'.$s.'-hotspot-'.$j.'">Hotspot '.$j.'</span></li>';
1093
  }
1094
  else {
1095
- $html .= '<li><span data-index="'.$j.'" data-href="#scene-'.$s.'-hotspot-'.$j.'">Hotspot '.$j.'</span></li>';
1096
  }
1097
  $j++;
1098
  }
1099
- $html .= '<li class="add" data-repeater-create><span><i class="fa fa-plus-circle"></i> Add</span></li>';
1100
  $html .= '</ul>';
1101
  $html .= '</nav>';
1102
 
@@ -1125,7 +1375,7 @@ class Wpvr_Admin {
1125
  if ($pano_hotspot['hotspot-title'] == $pano_hotspots[0]['hotspot-title']) {
1126
  $html .= '<div data-repeater-item class="single-hotspot rex-pano-tab active clearfix" id="scene-'.$s.'-hotspot-'.$h.'">';
1127
 
1128
- $html .= '<h6 class="title"><i class="fa fa-cog"></i> Hotspot <span class="hotspot-num">'.$h.'</span> Setting for <span>Scene <span class="scene-num">'.$s.'</span></span> </h6>';
1129
 
1130
  $html .= '<div class="wrapper">';
1131
  $html .= '<div class="hotspot-setting">';
@@ -1134,13 +1384,13 @@ class Wpvr_Admin {
1134
  $html .= '</div>';
1135
 
1136
  $html .= '<div class="hotspot-setting">';
1137
- $html .= '<label for="hotspot-pitch">'.__('Pitch: ','wpvr').' </label>';
1138
- $html .= '<input type="text" id="hotspot-pitch" name="hotspot-pitch" value="'.$hotspot_pitch.'" />';
1139
  $html .= '</div>';
1140
 
1141
  $html .= '<div class="hotspot-setting">';
1142
- $html .= '<label for="hotspot-yaw">'.__('Yaw: ','wpvr').' </label>';
1143
- $html .= '<input type="text" id="hotspot-yaw" name="hotspot-yaw" value="'.$hotspot_yaw.'" />';
1144
  $html .= '</div>';
1145
 
1146
  $html .= '<div class="hotspot-setting">';
@@ -1174,9 +1424,16 @@ class Wpvr_Admin {
1174
  $html .= '<textarea name="hotspot-hover">'.$hotspot_hover.'</textarea>';
1175
  $html .= '</div>';
1176
 
 
 
 
 
 
 
 
1177
  $html .= '<div class="hotspot-scene" style="display:none;" >';
1178
  $html .= '<label for="hotspot-scene">'.__('Target Scene ID: ','wpvr').' </label>';
1179
- $html .= '<input type="text" name="hotspot-scene"/>';
1180
  $html .= '</div>';
1181
 
1182
  $html .= '</div>';
@@ -1186,7 +1443,7 @@ class Wpvr_Admin {
1186
 
1187
  $html .= '<div class="hotspot-type hotspot-setting">';
1188
  $html .= '<label for="hotspot-type">'.__('Hotspot-Type: ','wpvr').'</label>';
1189
- $html .= '<select name="hotspot-type">';
1190
  $html .= '<option value="info"> Info</option>';
1191
  $html .= '<option value="scene" selected> Scene</option>';
1192
  $html .= '</select>';
@@ -1196,7 +1453,7 @@ class Wpvr_Admin {
1196
  $html .= '<input type="url" name="hotspot-url" />';
1197
  $html .= '</div>';
1198
 
1199
- $html .= '<div class="hotspot-content" style="display:none;>';
1200
  $html .= '<label for="hotspot-content">'.__('On click Content: ','wpvr').'</label>';
1201
  $html .= '<textarea name="hotspot-content"></textarea>';
1202
  $html .= '</div>';
@@ -1206,9 +1463,16 @@ class Wpvr_Admin {
1206
  $html .= '<textarea name="hotspot-hover">'.$hotspot_hover.'</textarea>';
1207
  $html .= '</div>';
1208
 
 
 
 
 
 
 
 
1209
  $html .= '<div class="hotspot-scene">';
1210
  $html .= '<label for="hotspot-scene">'.__('Target Scene ID: ','wpvr').'</label>';
1211
- $html .= '<input type="text" name="hotspot-scene" value="'.$hotspot_target_scene.'" />';
1212
  $html .= '</div>';
1213
 
1214
  $html .= '</div>';
@@ -1221,7 +1485,7 @@ class Wpvr_Admin {
1221
  else {
1222
  $html .= '<div data-repeater-item class="single-hotspot rex-pano-tab clearfix" id="scene-'.$s.'-hotspot-'.$h.'">';
1223
 
1224
- $html .= '<h6 class="title"><i class="fa fa-cog"></i> Hotspot <span class="hotspot-num">'.$h.'</span> Setting for <span>Scene <span class="scene-num">'.$s.'</span></span> </h6>';
1225
 
1226
  $html .= '<div class="wrapper">';
1227
  $html .= '<div class="hotspot-setting">';
@@ -1230,13 +1494,13 @@ class Wpvr_Admin {
1230
  $html .= '</div>';
1231
 
1232
  $html .= '<div class="hotspot-setting">';
1233
- $html .= '<label for="hotspot-pitch">'.__('Pitch: ','wpvr').' </label>';
1234
- $html .= '<input type="text" id="hotspot-pitch" name="hotspot-pitch" value="'.$hotspot_pitch.'" />';
1235
  $html .= '</div>';
1236
 
1237
  $html .= '<div class="hotspot-setting">';
1238
- $html .= '<label for="hotspot-yaw">'.__('Yaw: ','wpvr').' </label>';
1239
- $html .= '<input type="text" id="hotspot-yaw" name="hotspot-yaw" value="'.$hotspot_yaw.'" />';
1240
  $html .= '</div>';
1241
 
1242
  $html .= '<div class="hotspot-setting">';
@@ -1270,9 +1534,16 @@ class Wpvr_Admin {
1270
  $html .= '<textarea name="hotspot-hover">'.$hotspot_hover.'</textarea>';
1271
  $html .= '</div>';
1272
 
 
 
 
 
 
 
 
1273
  $html .= '<div class="hotspot-scene" style="display:none;" >';
1274
  $html .= '<label for="hotspot-scene">'.__('Target Scene ID: ','wpvr').' </label>';
1275
- $html .= '<input type="text" name="hotspot-scene"/>';
1276
  $html .= '</div>';
1277
 
1278
  $html .= '</div>';
@@ -1282,7 +1553,7 @@ class Wpvr_Admin {
1282
 
1283
  $html .= '<div class="hotspot-type hotspot-setting">';
1284
  $html .= '<label for="hotspot-type">'.__('Hotspot-Type: ','wpvr').'</label>';
1285
- $html .= '<select name="hotspot-type">';
1286
  $html .= '<option value="info"> Info</option>';
1287
  $html .= '<option value="scene" selected> Scene</option>';
1288
  $html .= '</select>';
@@ -1292,7 +1563,7 @@ class Wpvr_Admin {
1292
  $html .= '<input type="url" name="hotspot-url" />';
1293
  $html .= '</div>';
1294
 
1295
- $html .= '<div class="hotspot-content" style="display:none;>';
1296
  $html .= '<label for="hotspot-content">'.__('On click Content: ','wpvr').' </label>';
1297
  $html .= '<textarea name="hotspot-content"></textarea>';
1298
  $html .= '</div>';
@@ -1302,9 +1573,16 @@ class Wpvr_Admin {
1302
  $html .= '<textarea name="hotspot-hover">'.$hotspot_hover.'</textarea>';
1303
  $html .= '</div>';
1304
 
 
 
 
 
 
 
 
1305
  $html .= '<div class="hotspot-scene">';
1306
  $html .= '<label for="hotspot-scene">'.__('Target Scene ID: ','wpvr').' </label>';
1307
- $html .= '<input type="text" name="hotspot-scene" value="'.$hotspot_target_scene.'" />';
1308
  $html .= '</div>';
1309
 
1310
  $html .= '</div>';
@@ -1319,80 +1597,6 @@ class Wpvr_Admin {
1319
  $html .= '</div>';
1320
  $html .= '</div>';
1321
  }
1322
- // else {
1323
- // $html .= '<div class="hotspot-setup rex-pano-sub-tabs" data-limit="'.$data_limit.'">';
1324
-
1325
- // $html .= '<nav class="rex-pano-tab-nav rex-pano-nav-menu hotspot-nav">';
1326
- // $html .= '<ul>';
1327
- // $html .= '<li class="active"><span data-index="1" data-href="#scene-'.$s.'-hotspot-1">Hotspot 1</span></li>';
1328
- // $html .= '<li class="add" data-repeater-create><span><i class="fa fa-plus-circle"></i> Add</span></li>';
1329
- // $html .= '</ul>';
1330
- // $html .= '</nav>';
1331
-
1332
- // $html .= '<div data-repeater-list="hotspot-list" class="rex-pano-tab-content>';
1333
-
1334
- // $html .= '<div data-repeater-item class="single-hotspot rex-pano-tab active clearfix" id="scene-'.$s.'-hotspot-1">';
1335
-
1336
- // $html .= '<h6 class="title"><i class="fa fa-cog"></i> Hotspot <span class="hotspot-num">1</span> Setting for <span>Scene <span class="scene-num">'.$s.'</span></span> </h6>';
1337
-
1338
- // $html .= '<div class="wrapper">';
1339
- // $html .= '<div class="hotspot-setting">';
1340
- // $html .= '<label for="hotspot-title">'.__('Hotspot ID : ','wpvr').'</label>';
1341
- // $html .= '<input type="text" id="hotspot-title" name="hotspot-title"/>';
1342
- // $html .= '</div>';
1343
-
1344
- // $html .= '<div class="hotspot-setting">';
1345
- // $html .= '<label for="hotspot-pitch">'.__('Pitch: ','wpvr').' </label>';
1346
- // $html .= '<input type="text" id="hotspot-pitch" name="hotspot-pitch"/>';
1347
- // $html .= '</div>';
1348
-
1349
- // $html .= '<div class="hotspot-setting">';
1350
- // $html .= '<label for="hotspot-yaw">'.__('Yaw: ','wpvr').' </label>';
1351
- // $html .= '<input type="text" id="hotspot-yaw" name="hotspot-yaw"/>';
1352
- // $html .= '</div>';
1353
-
1354
- // $html .= '<div class="hotspot-setting">';
1355
- // $html .= '<label for="hotspot-customclass">'.__('Hotspot custom class: ','wpvr').'</label>';
1356
- // $html .= '<input type="text" id="hotspot-customclass" name="hotspot-customclass"/>';
1357
- // $html .= '</div>';
1358
- // $html .= '</div>';
1359
-
1360
- // $html .= '<div class="hotspot-type hotspot-setting">';
1361
- // $html .= '<label for="hotspot-type">'.__('Hotspot-Type: ','wpvr').'</label>';
1362
- // $html .= '<select name="hotspot-type">';
1363
- // $html .= '<option value="info" selected> Info</option>';
1364
- // $html .= '<option value="scene"> Scene</option>';
1365
- // $html .= '</select>';
1366
-
1367
- // $html .= '<div class="hotspot-url">';
1368
- // $html .= '<label for="hotspot-url">'.__('URL: ','wpvr').'</label>';
1369
- // $html .= '<input type="url" name="hotspot-url" />';
1370
- // $html .= '</div>';
1371
-
1372
- // $html .= '<div class="hotspot-content">';
1373
- // $html .= '<label for="hotspot-content">'.__(' On click Content: ','wpvr').'</label>';
1374
- // $html .= '<textarea name="hotspot-content"></textarea>';
1375
- // $html .= '</div>';
1376
-
1377
- // $html .= '<div class="hotspot-hover">';
1378
- // $html .= '<label for="hotspot-hover">'.__('On hover Content: ','wpvr').' </label>';
1379
- // $html .= '<textarea name="hotspot-hover"></textarea>';
1380
- // $html .= '</div>';
1381
-
1382
- // $html .= '<div class="hotspot-scene" style="display:none;" >';
1383
- // $html .= '<label for="hotspot-scene">'.__('Target Scene ID: ','wpvr').' </label>';
1384
- // $html .= '<input type="text" name="hotspot-scene"/>';
1385
- // $html .= '</div>';
1386
-
1387
- // $html .= '</div>';
1388
- // //=Hotspot type End=//
1389
- // $html .= '<button data-repeater-delete type="button" title="Delete Hotspot" class="delete-hotspot"><i class="fa fa-trash-o"></i></button>';
1390
- // $html .= '</div>';
1391
-
1392
- // $html .= '</div>';
1393
- // //$html .= '<input data-repeater-create type="button" value="Add Hotspot"/>';
1394
- // $html .= '</div>';
1395
- // }
1396
  $html .= '<button data-repeater-delete type="button" title="Delete Scene" class="delete-scene"><i class="fa fa-trash-o"></i></button>';
1397
  $html .= '</div>';
1398
  }
@@ -1405,12 +1609,100 @@ class Wpvr_Admin {
1405
 
1406
  $html .= '<div class="preview-btn-wrapper">';
1407
  $html .= '<div class="preview-btn-area clearfix">';
1408
- $html .= '<div id="error_occured"></div>';
1409
  $html .= '<button id="panolenspreview">'.__('Preview','wpvr').'</button>';
1410
  $html .= '</div>';
1411
  $html .= '</div>';
1412
  $html .='</div>';
1413
  //---end scenes tab----
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1414
 
1415
  $html .='</div>';
1416
  //---end rex-pano-tab-content----
88
  wp_enqueue_style( 'materialize-css', plugin_dir_url( __FILE__ ) . 'css/materialize.min.css', array(), $this->version, 'all' );
89
  }
90
  wp_enqueue_style('panellium-css', plugin_dir_url( __FILE__ ) . 'lib/pannellum/src/css/pannellum.css', array(), true);
91
+ wp_enqueue_style('videojs-css', 'https://vjs.zencdn.net/7.1.0/video-js.css', array(), true);
92
  wp_enqueue_style( $this->plugin_name, plugin_dir_url( __FILE__ ) . 'css/wpvr-admin.css', array(), $this->version, 'all' );
93
 
94
  }
111
  * between the defined hooks and the functions defined in this
112
  * class.
113
  */
114
+
115
  wp_enqueue_media();
 
116
  wp_enqueue_script('panellium-js', plugin_dir_url( __FILE__ ) . 'lib/pannellum/src/js/pannellum.js', array(), true);
117
  wp_enqueue_script('panelliumlib-js', plugin_dir_url( __FILE__ ) . 'lib/pannellum/src/js/libpannellum.js', array(), true);
118
+ wp_enqueue_script( 'videojs-js', plugin_dir_url( __FILE__ ) .'js/video.js', array('jquery'), true);
119
+ wp_enqueue_script('panelliumvid-js', plugin_dir_url( __FILE__ ) . 'lib/pannellum/src/js/videojs-pannellum-plugin.js', array(), true);
120
+ $adscreen = get_current_screen();
121
+
122
+ if ($adscreen->id=="toplevel_page_wpvr") {
123
+ wp_enqueue_script( 'materialize-js', plugin_dir_url( __FILE__ ) . 'js/materialize.min.js', array( 'jquery' ), $this->version, false );
124
+ }
125
  wp_enqueue_script( 'jquery-repeater', plugin_dir_url( __FILE__ ) .'js/jquery.repeater.min.js', array('jquery'), true);
126
  wp_enqueue_script( $this->plugin_name, plugin_dir_url( __FILE__ ) . 'js/wpvr-admin.js', array( 'jquery' ), $this->version, false );
127
  wp_localize_script( $this->plugin_name, 'wpvr_obj', array(
275
  else {
276
  $data_limit = 5;
277
  }
278
+ $scene_limit = $data_limit + 1;
279
  $postdata = get_post_meta( $post->ID, 'panodata', true );
280
+
281
+
282
  $autoload = true;
283
  if (isset($postdata["autoLoad"])) {
284
  $autoload = $postdata["autoLoad"];
285
  }
286
 
287
+ $control = true;
288
  if (isset($postdata["showControls"])) {
289
  $control = $postdata["showControls"];
290
  }
293
  if (isset($postdata["defaultscene"])) {
294
  $default_scene = $postdata["defaultscene"];
295
  }
296
+
297
+ $preview = '';
298
+ if (isset($postdata['preview'])) {
299
+ $preview = $postdata['preview'];
300
+ }
301
+
302
+ $autorotation = '';
303
+ if (isset($postdata["autoRotate"])) {
304
+ $autorotation = $postdata["autoRotate"];
305
+ }
306
+ $autorotationinactivedelay = '';
307
+ if (isset($postdata["autoRotateInactivityDelay"])) {
308
+ $autorotationinactivedelay = $postdata["autoRotateInactivityDelay"];
309
+ }
310
+ $autorotationstopdelay = '';
311
+ if (isset($postdata["autoRotateStopDelay"])) {
312
+ $autorotationstopdelay = $postdata["autoRotateStopDelay"];
313
+ }
314
 
315
  $scene_fade_duration = '';
316
  if (isset($postdata["scenefadeduration"])) {
337
  $html .='<li class="general active"><span data-href="#general"><i class="fa fa-cogs"></i> '.__('general','wpvr').'</span></li>';
338
  $html .='<li class="scene"><span data-href="#scenes"><i class="fa fa-photo"></i> '.__('Scenes','wpvr').'</span></li>';
339
  $html .='<li class="hotspot"><span data-href="#scenes"><i class="fa fa-dot-circle-o"></i> '.__('hotspot','wpvr').'</span></li>';
340
+ $html .='<li class="video"><span data-href="#video"><i class="fa fa-video-camera"></i> '.__('Video','wpvr').'</span></li>';
341
  $html .='</ul>';
342
  $html .='</nav>';
343
 
345
  $html .='<div class="rex-pano-tab general active" id="general">';
346
 
347
  $html .= '<h6 class="title"> '.__('General Settings : ','wpvr').'</h6>';
348
+
349
  //=Autoload setup=
350
  if ($autoload == true) {
351
  $html .= '<div class="single-settings autoload">';
382
  //=Autoload setup End=
383
 
384
  //=Control Setup=
385
+ if ($control == false) {
386
  $html .= '<div class="single-settings controls">';
387
  $html .= '<span>'.__('Show Controls: ','wpvr').'</span>';
388
  $html .= '<ul>';
389
  $html .= '<li class="radio-btn">';
390
+ $html .= '<input class="styled-radio" id="styled-radio-3" type="radio" name="controls" value="off" checked>';
391
  $html .= '<label for="styled-radio-3">Off</label>';
392
  $html .= '</li>';
393
 
394
  $html .= '<li class="radio-btn">';
395
+ $html .= '<input class="styled-radio" id="styled-radio-4" type="radio" name="controls" value="on" >';
396
  $html .= '<label for="styled-radio-4">On</label>';
397
  $html .= '</li>';
398
  $html .= '</ul>';
403
  $html .= '<span>'.__('Show Controls: ','wpvr').'</span>';
404
  $html .= '<ul>';
405
  $html .= '<li class="radio-btn">';
406
+ $html .= '<input class="styled-radio" id="styled-radio-3" type="radio" name="controls" value="off" >';
407
  $html .= '<label for="styled-radio-3">Off</label>';
408
  $html .= '</li>';
409
 
410
  $html .= '<li class="radio-btn">';
411
+ $html .= '<input class="styled-radio" id="styled-radio-4" type="radio" name="controls" value="on" checked>';
412
  $html .= '<label for="styled-radio-4">On</label>';
413
  $html .= '</li>';
414
  $html .= '</ul>';
415
  $html .= '</div>';
416
  }
417
+ //=Control setup End=//
418
+
419
+ //=Auto Rotation=//
420
+ $html .= '<div class="single-settings scene-fade-duration">';
421
+ $html .= '<span>'.__('Auto Rotation: ','wpvr').'</span>';
422
+ $html .= '<input type="number" name="auto-rotation" value="'.$autorotation.'" />';
423
+ $html .= '<div class="field-tooltip">';
424
+ $html .= '<i class="fa fa-question-circle"></i>';
425
+ $html .= '<span>'.__('Will automatically rotate the panorama for each page load. You can define rotation speed with number values. Positive number for counter-clockwise and negative number for clockwise. As an example "-5" will rotate the panorama clockwise.','wpvr').'</span>';
426
+ $html .= '</div>';
427
+ $html .= '</div>';
428
+ //=Auto Rotation=//
429
+
430
+ //=Auto rotation inactive delay=//
431
+ $html .= '<div class="single-settings scene-fade-duration">';
432
+ $html .= '<span>'.__('Auto Rotation Inactive Delay: ','wpvr').'</span>';
433
+ $html .= '<input type="number" name="auto-rotation-inactive-delay" value="'.$autorotationinactivedelay.'" />';
434
+ $html .= '<div class="field-tooltip">';
435
+ $html .= '<i class="fa fa-question-circle"></i>';
436
+ $html .= '<span>'.__('Will pause the rotation for few times. You can put the time value in miliseconds. As an example "2000" will pause the rotation for 2 seconds.','wpvr').'</span>';
437
+ $html .= '</div>';
438
+ $html .= '</div>';
439
+ //=Auto rotation inactive delay=//
440
+
441
+ //=Auto rotation stop delay=//
442
+ $html .= '<div class="single-settings scene-fade-duration">';
443
+ $html .= '<span>'.__('Auto Rotation Stop Delay: ','wpvr').'</span>';
444
+ $html .= '<input type="number" name="auto-rotation-stop-delay" value="'.$autorotationstopdelay.'" />';
445
+ $html .= '<div class="field-tooltip">';
446
+ $html .= '<i class="fa fa-question-circle"></i>';
447
+ $html .= '<span>'.__('Will stop the auto rotation after given time value. As an example for "2000" the roation will stop after 2 seconds on each page load.','wpvr').'</span>';
448
+ $html .= '</div>';
449
+ $html .= '</div>';
450
+ //=Auto rotation stop delay=//
451
 
452
+ //=scene fade duration=//
453
  $html .= '<div class="single-settings scene-fade-duration">';
454
  $html .= '<span>'.__('Scene Fade Duration: ','wpvr').'</span>';
455
  $html .= '<input type="number" name="scene-fade-duration" value="'.$scene_fade_duration.'" />';
456
+ // $html .= '<div class="field-tooltip">';
457
+ // $html .= '<i class="fa fa-question-circle"></i>';
458
+ // $html .= '<span>'.__('This is Scene fade duration tooltip','wpvr').'</span>';
459
+ // $html .= '</div>';
460
  $html .= '</div>';
461
+ //=scene fade duration End=//
462
+
463
+
464
+ //===preview image===//
465
+ if (!empty($preview)) {
466
+ $html .= '<div class="single-settings preview-setting">';
467
+ $html .= '<span>'.__('Preview Upload or add link : ','wpvr').'</span>';
468
+ $html .= '<div class="form-group">';
469
+ $html .= '<img class="prev-img" src="'.$preview.'">';
470
+ $html .= '<input type="text" name="preview-attachment-url" class="preview-attachment-url" value="'.$preview.'"><br>';
471
+ $html .= '<input type="button" class="preview-upload" data-info="" value="Upload"/>';
472
+ $html .= '</div>';
473
+ $html .= '</div>';
474
+ }
475
+ else {
476
+ $html .= '<div class="single-settings preview-setting">';
477
+ $html .= '<span>'.__('Preview Upload or add link : ','wpvr').'</span>';
478
+ $html .= '<div class="form-group">';
479
+ $html .= '<img class="prev-img" src="" style="display: none;">';
480
+ $html .= '<input type="text" name="preview-attachment-url" class="preview-attachment-url" value=""><br>';
481
+ $html .= '<input type="button" class="preview-upload" data-info="" value="Upload"/>';
482
+ $html .= '</div>';
483
+ $html .= '</div>';
484
+ }
485
+ //===preview image end===//
486
 
487
  $html .='</div>';
488
  //---end general tab----
491
 
492
  //=Scene and Hotspot repeater=//
493
  if (empty($pano_data)) {
494
+ $html .= '<div class="scene-setup rex-pano-sub-tabs" data-limit="'.$scene_limit.'">';
495
 
496
  $html .= '<nav class="rex-pano-tab-nav rex-pano-nav-menu scene-nav">';
497
  $html .= '<ul>';
498
+ $html .= '<li class="active"><span data-index="1" data-href="#scene-1"><i class="fa fa-photo"></i></span></li>';
499
+ $html .= '<li class="add" data-repeater-create><span><i class="fa fa-plus-circle"></i></span></li>';
500
  $html .= '</ul>';
501
  $html .= '</nav>';
502
 
503
  $html .= '<div data-repeater-list="scene-list" class="rex-pano-tab-content">';
504
+
505
+ $html .= '<div data-repeater-item class="single-scene rex-pano-tab" data-title="0" id="scene-0">';
506
+
507
+ $html .= '<div class="scene-content">';
508
+ $html .= '<h6 class="title"><i class="fa fa-cog"></i> Scene Setting </h6>';
509
+
510
+ //==Set Default Scene==//
511
+ $html .= '<div class="single-settings dscene">';
512
+ $html .= '<span>'.__('Set as default: ','wpvr').'</span>';
513
+ $html .= '<select class="dscen" name="dscene">';
514
+ $html .= '<option value="on"> Yes</option>';
515
+ $html .= '<option value="off" selected > No</option>';
516
+ $html .= '</select>';
517
+ $html .= '</div>';
518
+ //==Set Default Scene end==//
519
+ $html .= '<div class=scene-setting>';
520
+ $html .= '<label for="scene-id">'.__('Scene ID : ','wpvr').'</label>';
521
+ $html .= '<input class="sceneid" type="text" name="scene-id"/>';
522
+ $html .= '</div>';
523
+
524
+ $html .= '<div class=scene-setting>';
525
+ $html .= '<label for="scene-type">'.__('Scene Type : ','wpvr').'</label>';
526
+ $html .= '<input type="text" name="scene-type" value="equirectangular" disabled/>';
527
+ $html .= '</div>';
528
+
529
+ $html .= '<div class=scene-setting>';
530
+ $html .= '<label for="scene-upload">'.__('Scene Upload: ','wpvr').'</label>';
531
+ $html .= '<div class="form-group">';
532
+ $html .= '<img src="" style="display: none;"><br>';
533
+ $html .= '<input type="button" class="scene-upload" data-info="" value="Upload"/>';
534
+ $html .= '<input type="hidden" name="scene-attachment-url" class="scene-attachment-url" value="">';
535
+ $html .= '</div>';
536
+ $html .= '</div>';
537
+ $html .= '</div>';
538
+
539
+ //--hotspot setup--
540
+ $html .= '<div class="hotspot-setup rex-pano-sub-tabs" data-limit="'.$data_limit.'">';
541
+
542
+ $html .= '<nav class="rex-pano-tab-nav rex-pano-nav-menu hotspot-nav">';
543
+ $html .= '<ul>';
544
+ $html .= '<li class="active"><span data-index="1" data-href="#scene-0-hotspot-1"><i class="fa fa-dot-circle-o"></i></span></li>';
545
+ $html .= '<li class="add" data-repeater-create><span><i class="fa fa-plus-circle"></i> </span></li>';
546
+ $html .= '</ul>';
547
+ $html .= '</nav>';
548
+
549
+ $html .= '<div data-repeater-list="hotspot-list" class="rex-pano-tab-content">';
550
+ $html .= '<div data-repeater-item class="single-hotspot rex-pano-tab active clearfix" id="scene-0-hotspot-1">';
551
+
552
+ $html .= '<h6 class="title"><i class="fa fa-cog"></i> Hotspot Setting </h6>';
553
+
554
+ $html .= '<div class="wrapper">';
555
+ $html .= '<div class="hotspot-setting">';
556
+ $html .= '<label for="hotspot-title">'.__('Hotspot ID : ','wpvr').'</label>';
557
+ $html .= '<input type="text" id="hotspot-title" name="hotspot-title"/>';
558
+ $html .= '</div>';
559
+
560
+ $html .= '<div class="hotspot-setting">';
561
+ $html .= '<label for="hotspot-pitch">'.__('Pitch: ','wpvr').'</label>';
562
+ $html .= '<input type="text" class="hotspot-pitch" name="hotspot-pitch"/>';
563
+ $html .= '</div>';
564
+
565
+ $html .= '<div class="hotspot-setting">';
566
+ $html .= '<label for="hotspot-yaw">'.__('Yaw: ','wpvr').'</label>';
567
+ $html .= '<input type="text" class="hotspot-yaw" name="hotspot-yaw"/>';
568
+ $html .= '</div>';
569
+
570
+ $html .= '<div class="hotspot-setting">';
571
+ $html .= '<label for="hotspot-customclass">'.__('Hotspot custom class: ','wpvr').'</label>';
572
+ $html .= '<input type="text" id="hotspot-customclass" name="hotspot-customclass"/>';
573
+ $html .= '</div>';
574
+ $html .= '</div>';
575
+
576
+ $html .= '<div class="hotspot-type hotspot-setting">';
577
+ $html .= '<label for="hotspot-type">'.__('Hotspot-Type: ','wpvr').'</label>';
578
+ $html .= '<select name="hotspot-type">';
579
+ $html .= '<option value="info" selected> Info</option>';
580
+ $html .= '<option value="scene"> Scene</option>';
581
+ $html .= '</select>';
582
+
583
+ $html .= '<div class="hotspot-url">';
584
+ $html .= '<label for="hotspot-url">'.__('URL: ','wpvr').' </label>';
585
+ $html .= '<input type="url" name="hotspot-url" value="" />';
586
+ $html .= '</div>';
587
+
588
+ $html .= '<div class="hotspot-content">';
589
+ $html .= '<label for="hotspot-content">'.__('On click Content: ','wpvr').'</label>';
590
+ $html .= '<textarea name="hotspot-content"></textarea>';
591
+ $html .= '</div>';
592
+
593
+ $html .= '<div class="hotspot-hover">';
594
+ $html .= '<label for="hotspot-hover">'.__('On hover Content: ','wpvr').'</label>';
595
+ $html .= '<textarea name="hotspot-hover"></textarea>';
596
+ $html .= '</div>';
597
+
598
+ $html .= '<div class="hotspot-scene" style="display:none;" >';
599
+ $html .= '<label for="hotspot-scene">'.__('Select Target Scene from List: ','wpvr').'</label>';
600
+ $html .= '<select class="hotspotscene" name="hotspot-scene-list">';
601
+ $html .= '<option value="none" selected> None</option>';
602
+ $html .= '</select>';
603
+ $html .= '</div>';
604
+
605
+ $html .= '<div class="hotspot-scene" style="display:none;" >';
606
+ $html .= '<label for="hotspot-scene">'.__('Target Scene ID: ','wpvr').' </label>';
607
+ $html .= '<input class="hotspotsceneinfodata" type="text" name="hotspot-scene" disabled/>';
608
+ $html .= '</div>';
609
+
610
+ $html .= '</div>';
611
+ //=Hotspot type End=//
612
+ $html .= '<button data-repeater-delete title="Delete Hotspot" type="button" class="delete-hotspot"><i class="fa fa-trash-o"></i></button>';
613
+ $html .= '</div>';
614
+ $html .= '</div>';
615
+
616
+ $html .= '</div>';
617
+ $html .= '<button data-repeater-delete type="button" title="Delete Scene" class="delete-scene"><i class="fa fa-trash-o"></i></button>';
618
+ $html .= '</div>';
619
+
620
+
621
  $html .= '<div data-repeater-item class="single-scene rex-pano-tab active" data-title="1" id="scene-1">';
622
 
623
  $html .= '<div class="scene-content">';
624
+ $html .= '<h6 class="title"><i class="fa fa-cog"></i> Scene Setting </h6>';
625
 
626
  //==Set Default Scene==//
627
  $html .= '<div class="single-settings dscene">';
635
 
636
  $html .= '<div class=scene-setting>';
637
  $html .= '<label for="scene-id">'.__('Scene ID : ','wpvr').'</label>';
638
+ $html .= '<input class="sceneid" type="text" name="scene-id"/>';
639
  $html .= '</div>';
640
 
641
  $html .= '<div class=scene-setting>';
658
 
659
  $html .= '<nav class="rex-pano-tab-nav rex-pano-nav-menu hotspot-nav">';
660
  $html .= '<ul>';
661
+ $html .= '<li class="active"><span data-index="1" data-href="#scene-1-hotspot-1"><i class="fa fa-dot-circle-o"></i></span></li>';
662
+ $html .= '<li class="add" data-repeater-create><span><i class="fa fa-plus-circle"></i> </span></li>';
663
  $html .= '</ul>';
664
  $html .= '</nav>';
665
 
666
  $html .= '<div data-repeater-list="hotspot-list" class="rex-pano-tab-content">';
667
  $html .= '<div data-repeater-item class="single-hotspot rex-pano-tab active clearfix" id="scene-1-hotspot-1">';
668
 
669
+ $html .= '<h6 class="title"><i class="fa fa-cog"></i> Hotspot Setting </h6>';
670
 
671
  $html .= '<div class="wrapper">';
672
  $html .= '<div class="hotspot-setting">';
676
 
677
  $html .= '<div class="hotspot-setting">';
678
  $html .= '<label for="hotspot-pitch">'.__('Pitch: ','wpvr').'</label>';
679
+ $html .= '<input type="text" class="hotspot-pitch" name="hotspot-pitch"/>';
680
  $html .= '</div>';
681
 
682
  $html .= '<div class="hotspot-setting">';
683
  $html .= '<label for="hotspot-yaw">'.__('Yaw: ','wpvr').'</label>';
684
+ $html .= '<input type="text" class="hotspot-yaw" name="hotspot-yaw"/>';
685
  $html .= '</div>';
686
 
687
  $html .= '<div class="hotspot-setting">';
712
  $html .= '<textarea name="hotspot-hover"></textarea>';
713
  $html .= '</div>';
714
 
715
+ $html .= '<div class="hotspot-scene" style="display:none;" >';
716
+ $html .= '<label for="hotspot-scene">'.__('Select Target Scene from List: ','wpvr').'</label>';
717
+ $html .= '<select class="hotspotscene" name="hotspot-scene-list">';
718
+ $html .= '<option value="none"> None</option>';
719
+ $html .= '</select>';
720
+ $html .= '</div>';
721
  $html .= '<div class="hotspot-scene" style="display:none;" >';
722
  $html .= '<label for="hotspot-scene">'.__('Target Scene ID: ','wpvr').'</label>';
723
+ $html .= '<input class="hotspotsceneinfodata" type="text" name="hotspot-scene" disabled/>';
724
  $html .= '</div>';
725
 
726
  $html .= '</div>';
737
  $html .= '</div>';
738
  }
739
  else {
740
+ $html .= '<div class="scene-setup rex-pano-sub-tabs" data-limit="'.$scene_limit.'">';
741
 
742
  $html .= '<nav class="rex-pano-tab-nav rex-pano-nav-menu scene-nav">';
743
  $html .= '<ul>';
744
+ // $html .= '<li><span data-index="0" data-href="#scene-0">Scene 0</span></li>';
745
  $i = 1;
746
+ $firstvalue = reset($pano_data["scene-list"]);
747
  foreach ($pano_data["scene-list"] as $pano_scenes) {
748
+ if ($pano_scenes['scene-id'] == $firstvalue['scene-id']) {
749
+ $html .= '<li class="active"><span data-index="'.$i.'" data-href="#scene-'.$i.'"><i class="fa fa-photo"></i></span></li>';
750
  }
751
  else {
752
+ $html .= '<li><span data-index="'.$i.'" data-href="#scene-'.$i.'"><i class="fa fa-photo"></i></span></li>';
753
  }
754
  $i++;
755
  }
756
+ $html .= '<li class="add" data-repeater-create><span><i class="fa fa-plus-circle"></i></span></li>';
757
  $html .= '</ul>';
758
  $html .= '</nav>';
759
 
764
  $html .= '<div data-repeater-item class="single-scene rex-pano-tab" data-title="0" id="scene-0">';
765
 
766
  $html .= '<div class="scene-content">';
767
+ $html .= '<h6 class="title"><i class="fa fa-cog"></i> Scene Setting </h6>';
768
 
769
  //==Set Default Scene==//
770
  $html .= '<div class="single-settings dscene">';
777
  //==Set Default Scene end==//
778
  $html .= '<div class=scene-setting>';
779
  $html .= '<label for="scene-id">'.__('Scene ID : ','wpvr').'</label>';
780
+ $html .= '<input class="sceneid" type="text" name="scene-id"/>';
781
  $html .= '</div>';
782
 
783
  $html .= '<div class=scene-setting>';
800
 
801
  $html .= '<nav class="rex-pano-tab-nav rex-pano-nav-menu hotspot-nav">';
802
  $html .= '<ul>';
803
+ $html .= '<li class="active"><span data-index="1" data-href="#scene-0-hotspot-1"><i class="fa fa-dot-circle-o"></i></span></li>';
804
+ $html .= '<li class="add" data-repeater-create><span><i class="fa fa-plus-circle"></i> </span></li>';
805
  $html .= '</ul>';
806
  $html .= '</nav>';
807
 
808
  $html .= '<div data-repeater-list="hotspot-list" class="rex-pano-tab-content">';
809
  $html .= '<div data-repeater-item class="single-hotspot rex-pano-tab active clearfix" id="scene-0-hotspot-1">';
810
 
811
+ $html .= '<h6 class="title"><i class="fa fa-cog"></i> Hotspot Setting </h6>';
812
 
813
  $html .= '<div class="wrapper">';
814
  $html .= '<div class="hotspot-setting">';
817
  $html .= '</div>';
818
 
819
  $html .= '<div class="hotspot-setting">';
820
+ $html .= '<label for="hotspot-pitch">'.__('Pitch: ','wpvr').'</label>';
821
+ $html .= '<input type="text" class="hotspot-pitch" name="hotspot-pitch"/>';
822
  $html .= '</div>';
823
 
824
  $html .= '<div class="hotspot-setting">';
825
+ $html .= '<label for="hotspot-yaw">'.__('Yaw: ','wpvr').'</label>';
826
+ $html .= '<input type="text" class="hotspot-yaw" name="hotspot-yaw"/>';
827
  $html .= '</div>';
828
 
829
  $html .= '<div class="hotspot-setting">';
853
  $html .= '<label for="hotspot-hover">'.__('On hover Content: ','wpvr').'</label>';
854
  $html .= '<textarea name="hotspot-hover"></textarea>';
855
  $html .= '</div>';
856
+
857
+ $html .= '<div class="hotspot-scene" style="display:none;" >';
858
+ $html .= '<label for="hotspot-scene">'.__('Select Target Scene from List: ','wpvr').'</label>';
859
+ $html .= '<select class="hotspotscene" name="hotspot-scene-list">';
860
+ $html .= '<option value="none" selected> None</option>';
861
+ $html .= '</select>';
862
+ $html .= '</div>';
863
 
864
  $html .= '<div class="hotspot-scene" style="display:none;" >';
865
  $html .= '<label for="hotspot-scene">'.__('Target Scene ID: ','wpvr').' </label>';
866
+ $html .= '<input class="hotspotsceneinfodata" type="text" name="hotspot-scene" disabled/>';
867
  $html .= '</div>';
868
 
869
  $html .= '</div>';
894
  $pano_hotspots = $pano_scenes["hotspot-list"];
895
  }
896
 
897
+ $firstvalueset = reset($pano_data["scene-list"]);
898
+ if ($pano_scenes['scene-id'] == $firstvalueset['scene-id']) {
899
  $html .= '<div data-repeater-item class="single-scene rex-pano-tab active" data-title="1" id="scene-'.$s.'">';
900
 
901
  $html .= '<div class="scene-content">';
902
+ $html .= '<h6 class="title"><i class="fa fa-cog"></i> Scene Setting </h6>';
903
  //==Set Default Scene==//
904
  if ($dscene == 'on') {
905
  $html .= '<div class="single-settings dscene">';
923
  //==Set Default Scene end==//
924
  $html .= '<div class=scene-setting>';
925
  $html .= '<label for="scene-id">'.__('Scene ID : ','wpvr').'</label>';
926
+ $html .= '<input class="sceneid" type="text" name="scene-id" value="'.$scene_id.'" />';
927
  $html .= '</div>';
928
 
929
  $html .= '<div class=scene-setting>';
947
  $html .= '<nav class="rex-pano-tab-nav rex-pano-nav-menu hotspot-nav">';
948
  $html .= '<ul>';
949
  $j = 1;
950
+ $firstvaluehotspot = reset($pano_hotspots);
951
  foreach ($pano_hotspots as $pano_hotspot) {
952
+
953
+ if ($pano_hotspot['hotspot-title'] == $firstvaluehotspot['hotspot-title']) {
954
+ $html .= '<li class="active"><span data-index="'.$j.'" data-href="#scene-'.$s.'-hotspot-'.$j.'"><i class="fa fa-dot-circle-o"></i></span></li>';
955
  }
956
  else {
957
+ $html .= '<li><span data-index="'.$j.'" data-href="#scene-'.$s.'-hotspot-'.$j.'"><i class="fa fa-dot-circle-o"></i></span></li>';
958
  }
959
  $j++;
960
  }
961
+ $html .= '<li class="add" data-repeater-create><span><i class="fa fa-plus-circle"></i></span></li>';
962
  $html .= '</ul>';
963
  $html .= '</nav>';
964
 
965
  $html .= '<div data-repeater-list="hotspot-list" class="rex-pano-tab-content">';
966
+
967
+
968
+ //==test hotspot==//
969
+ // $html .= '<div data-repeater-item class="single-hotspot rex-pano-tab clearfix" id="scene-'.$s.'-hotspot-0">';
970
+
971
+ // $html .= '<h6 class="title"><i class="fa fa-cog"></i> Hotspot Setting </h6>';
972
+
973
+ // $html .= '<div class="wrapper">';
974
+ // $html .= '<div class="hotspot-setting">';
975
+ // $html .= '<label for="hotspot-title">'.__('Hotspot ID : ','wpvr').'</label>';
976
+ // $html .= '<input type="text" id="hotspot-title" name="hotspot-title"/>';
977
+ // $html .= '</div>';
978
+
979
+ // $html .= '<div class="hotspot-setting">';
980
+ // $html .= '<label for="hotspot-pitch">'.__('Pitch: ','wpvr').'</label>';
981
+ // $html .= '<input type="text" class="hotspot-pitch" name="hotspot-pitch"/>';
982
+ // $html .= '</div>';
983
+
984
+ // $html .= '<div class="hotspot-setting">';
985
+ // $html .= '<label for="hotspot-yaw">'.__('Yaw: ','wpvr').'</label>';
986
+ // $html .= '<input type="text" class="hotspot-yaw" name="hotspot-yaw"/>';
987
+ // $html .= '</div>';
988
+
989
+ // $html .= '<div class="hotspot-setting">';
990
+ // $html .= '<label for="hotspot-customclass">'.__('Hotspot custom class: ','wpvr').'</label>';
991
+ // $html .= '<input type="text" id="hotspot-customclass" name="hotspot-customclass"/>';
992
+ // $html .= '</div>';
993
+ // $html .= '</div>';
994
+
995
+ // $html .= '<div class="hotspot-type hotspot-setting">';
996
+ // $html .= '<label for="hotspot-type">'.__('Hotspot-Type: ','wpvr').'</label>';
997
+ // $html .= '<select name="hotspot-type">';
998
+ // $html .= '<option value="info" selected> Info</option>';
999
+ // $html .= '<option value="scene"> Scene</option>';
1000
+ // $html .= '</select>';
1001
+
1002
+ // $html .= '<div class="hotspot-url">';
1003
+ // $html .= '<label for="hotspot-url">'.__('URL: ','wpvr').'</label>';
1004
+ // $html .= '<input type="url" name="hotspot-url" value="" />';
1005
+ // $html .= '</div>';
1006
+
1007
+ // $html .= '<div class="hotspot-content">';
1008
+ // $html .= '<label for="hotspot-content">'.__('On click Content: ','wpvr').'</label>';
1009
+ // $html .= '<textarea name="hotspot-content"></textarea>';
1010
+ // $html .= '</div>';
1011
+
1012
+ // $html .= '<div class="hotspot-hover">';
1013
+ // $html .= '<label for="hotspot-hover">'.__('On hover Content: ','wpvr').'</label>';
1014
+ // $html .= '<textarea name="hotspot-hover"></textarea>';
1015
+ // $html .= '</div>';
1016
+
1017
+ // $html .= '<div class="hotspot-scene" style="display:none;" >';
1018
+ // $html .= '<label for="hotspot-scene">'.__('Select Target Scene from List: ','wpvr').'</label>';
1019
+ // $html .= '<select class="hotspotscene" name="hotspot-scene-list">';
1020
+ // $html .= '<option value="none"> None</option>';
1021
+ // $html .= '</select>';
1022
+ // $html .= '</div>';
1023
+ // $html .= '<div class="hotspot-scene" style="display:none;" >';
1024
+ // $html .= '<label for="hotspot-scene">'.__('Target Scene ID: ','wpvr').'</label>';
1025
+ // $html .= '<input class="hotspotsceneinfodata" type="text" name="hotspot-scene" disabled/>';
1026
+ // $html .= '</div>';
1027
+
1028
+ // $html .= '</div>';
1029
+ // //=Hotspot type End=//
1030
+ // $html .= '<button data-repeater-delete title="Delete Hotspot" type="button" class="delete-hotspot"><i class="fa fa-trash-o"></i></button>';
1031
+ // $html .= '</div>';
1032
+ //==test hotspot==//
1033
+
1034
+
1035
+
1036
  $h = 1;
1037
+ $firstvaluehotspotset = reset($pano_hotspots);
1038
  foreach ($pano_hotspots as $pano_hotspot) {
1039
  $hotspot_title = '';
1040
  $hotspot_title = $pano_hotspot['hotspot-title'];
1054
  $hotspot_target_scene = $pano_hotspot['hotspot-scene'];
1055
  $hotspot_custom_class = '';
1056
  $hotspot_custom_class = $pano_hotspot['hotspot-customclass'];
1057
+ if ($pano_hotspot['hotspot-title'] == $firstvaluehotspotset['hotspot-title']) {
1058
  $html .= '<div data-repeater-item class="single-hotspot rex-pano-tab active clearfix" id="scene-'.$s.'-hotspot-'.$h.'">';
1059
 
1060
+ $html .= '<h6 class="title"><i class="fa fa-cog"></i> Hotspot Setting </h6>';
1061
 
1062
  $html .= '<div class="wrapper">';
1063
  $html .= '<div class="hotspot-setting">';
1066
  $html .= '</div>';
1067
 
1068
  $html .= '<div class="hotspot-setting">';
1069
+ $html .= '<label for="hotspot-pitch">'.__('Pitch: ','wpvr').'
1070
+ </label>';
1071
+ $html .= '<input type="text" class="hotspot-pitch" name="hotspot-pitch" value="'.$hotspot_pitch.'" />';
1072
  $html .= '</div>';
1073
 
1074
  $html .= '<div class="hotspot-setting">';
1075
  $html .= '<label for="hotspot-yaw">'.__('Yaw: ','wpvr').'</label>';
1076
+ $html .= '<input type="text" class="hotspot-yaw" name="hotspot-yaw" value="'.$hotspot_yaw.'" />';
1077
  $html .= '</div>';
1078
 
1079
  $html .= '<div class="hotspot-setting">';
1107
  $html .= '<textarea name="hotspot-hover">'.$hotspot_hover.'</textarea>';
1108
  $html .= '</div>';
1109
 
1110
+ $html .= '<div class="hotspot-scene" style="display:none;" >';
1111
+ $html .= '<label for="hotspot-scene">'.__('Select Target Scene from List: ','wpvr').'</label>';
1112
+ $html .= '<select class="hotspotscene" name="hotspot-scene-list">';
1113
+ $html .= '<option value="none" selected> None</option>';
1114
+ $html .= '</select>';
1115
+ $html .= '</div>';
1116
+
1117
  $html .= '<div class="hotspot-scene" style="display:none;" >';
1118
  $html .= '<label for="hotspot-scene"> '.__('Target Scene ID: ','wpvr').'</label>';
1119
+ $html .= '<input class="hotspotsceneinfodata" type="text" name="hotspot-scene" disabled/>';
1120
  $html .= '</div>';
1121
 
1122
  $html .= '</div>';
1126
 
1127
  $html .= '<div class="hotspot-type hotspot-setting">';
1128
  $html .= '<label for="hotspot-type">'.__('Hotspot-Type: ','wpvr').'</label>';
1129
+ $html .= '<select class="trtr" name="hotspot-type">';
1130
  $html .= '<option value="info"> Info</option>';
1131
  $html .= '<option value="scene" selected> Scene</option>';
1132
  $html .= '</select>';
1136
  $html .= '<input type="url" name="hotspot-url" />';
1137
  $html .= '</div>';
1138
 
1139
+ $html .= '<div class="hotspot-content" style="display:none;">';
1140
  $html .= '<label for="hotspot-content">'.__('On click Content: ','wpvr').' </label>';
1141
  $html .= '<textarea name="hotspot-content"></textarea>';
1142
  $html .= '</div>';
1146
  $html .= '<textarea name="hotspot-hover">'.$hotspot_hover.'</textarea>';
1147
  $html .= '</div>';
1148
 
1149
+ $html .= '<div class="hotspot-scene" >';
1150
+ $html .= '<label for="hotspot-scene">'.__('Select Target Scene from List: ','wpvr').'</label>';
1151
+ $html .= '<select class="hotspotscene" name="hotspot-scene-list">';
1152
+ $html .= '<option value="none" selected> None</option>';
1153
+ $html .= '</select>';
1154
+ $html .= '</div>';
1155
+
1156
  $html .= '<div class="hotspot-scene">';
1157
  $html .= '<label for="hotspot-scene">'.__('Target Scene ID: ','wpvr').'</label>';
1158
+ $html .= '<input class="hotspotsceneinfodata" type="text" name="hotspot-scene" value="'.$hotspot_target_scene.'" disabled />';
1159
  $html .= '</div>';
1160
 
1161
  $html .= '</div>';
1168
  else {
1169
  $html .= '<div data-repeater-item class="single-hotspot rex-pano-tab clearfix" id="scene-'.$s.'-hotspot-'.$h.'">';
1170
 
1171
+ $html .= '<h6 class="title"><i class="fa fa-cog"></i> Hotspot Setting </h6>';
1172
 
1173
  $html .= '<div class="wrapper">';
1174
  $html .= '<div class="hotspot-setting">';
1177
  $html .= '</div>';
1178
 
1179
  $html .= '<div class="hotspot-setting">';
1180
+ $html .= '<label for="hotspot-pitch">'.__('Pitch: ','wpvr').'</label>';
1181
+ $html .= '<input type="text" class="hotspot-pitch" name="hotspot-pitch" value="'.$hotspot_pitch.'" />';
1182
  $html .= '</div>';
1183
 
1184
  $html .= '<div class="hotspot-setting">';
1185
+ $html .= '<label for="hotspot-yaw">'.__('Yaw: ','wpvr').'</label>';
1186
+ $html .= '<input type="text" class="hotspot-yaw" name="hotspot-yaw" value="'.$hotspot_yaw.'" />';
1187
  $html .= '</div>';
1188
 
1189
  $html .= '<div class="hotspot-setting">';
1217
  $html .= '<textarea name="hotspot-hover">'.$hotspot_hover.'</textarea>';
1218
  $html .= '</div>';
1219
 
1220
+ $html .= '<div class="hotspot-scene" style="display:none;" >';
1221
+ $html .= '<label for="hotspot-scene">'.__('Select Target Scene from List: ','wpvr').'</label>';
1222
+ $html .= '<select class="hotspotscene" name="hotspot-scene-list">';
1223
+ $html .= '<option value="none" selected> None</option>';
1224
+ $html .= '</select>';
1225
+ $html .= '</div>';
1226
+
1227
  $html .= '<div class="hotspot-scene" style="display:none;" >';
1228
  $html .= '<label for="hotspot-scene">'.__('Target Scene ID: ','wpvr').'</label>';
1229
+ $html .= '<input class="hotspotsceneinfodata" type="text" name="hotspot-scene" disabled />';
1230
  $html .= '</div>';
1231
 
1232
  $html .= '</div>';
1236
 
1237
  $html .= '<div class="hotspot-type hotspot-setting">';
1238
  $html .= '<label for="hotspot-type">'.__('Hotspot-Type: ','wpvr').'</label>';
1239
+ $html .= '<select class="trtr" name="hotspot-type">';
1240
  $html .= '<option value="info"> Info</option>';
1241
  $html .= '<option value="scene" selected> Scene</option>';
1242
  $html .= '</select>';
1246
  $html .= '<input type="url" name="hotspot-url" />';
1247
  $html .= '</div>';
1248
 
1249
+ $html .= '<div class="hotspot-content" style="display:none;">';
1250
  $html .= '<label for="hotspot-content">'.__('On click Content: ','wpvr').'</label>';
1251
  $html .= '<textarea name="hotspot-content"></textarea>';
1252
  $html .= '</div>';
1256
  $html .= '<textarea name="hotspot-hover">'.$hotspot_hover.'</textarea>';
1257
  $html .= '</div>';
1258
 
1259
+ $html .= '<div class="hotspot-scene" >';
1260
+ $html .= '<label for="hotspot-scene">'.__('Select Target Scene from List: ','wpvr').'</label>';
1261
+ $html .= '<select class="hotspotscene" name="hotspot-scene-list">';
1262
+ $html .= '<option value="none" selected> None</option>';
1263
+ $html .= '</select>';
1264
+ $html .= '</div>';
1265
+
1266
  $html .= '<div class="hotspot-scene">';
1267
  $html .= '<label for="hotspot-scene">'.__('Target Scene ID: ','wpvr').'</label>';
1268
+ $html .= '<input class="hotspotsceneinfodata" type="text" name="hotspot-scene" value="'.$hotspot_target_scene.'" disabled />';
1269
  $html .= '</div>';
1270
 
1271
  $html .= '</div>';
1280
  $html .= '</div>';
1281
  $html .= '</div>';
1282
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1283
  $html .= '<button data-repeater-delete type="button" title="Delete Scene" class="delete-scene"><i class="fa fa-trash-o"></i></button>';
1284
  $html .= '</div>';
1285
  }
1287
  $html .= '<div data-repeater-item class="single-scene rex-pano-tab" data-title="1" id="scene-'.$s.'">';
1288
 
1289
  $html .= '<div class="scene-content">';
1290
+ $html .= '<h6 class="title"><i class="fa fa-cog"></i> Scene Setting </h6>';
1291
 
1292
  //==Set Default Scene==//
1293
  if ($dscene == 'on') {
1313
 
1314
  $html .= '<div class=scene-setting>';
1315
  $html .= '<label for="scene-id">'.__('Scene ID : ','wpvr').'</label>';
1316
+ $html .= '<input class="sceneid" type="text" name="scene-id" value="'.$scene_id.'" />';
1317
  $html .= '</div>';
1318
 
1319
  $html .= '<div class=scene-setting>';
1339
  $j = 1;
1340
  foreach ($pano_hotspots as $pano_hotspot) {
1341
  if ($pano_hotspot['hotspot-title'] == $pano_hotspots[0]['hotspot-title']) {
1342
+ $html .= '<li class="active"><span data-index="'.$j.'" data-href="#scene-'.$s.'-hotspot-'.$j.'"><i class="fa fa-dot-circle-o"></i></span></li>';
1343
  }
1344
  else {
1345
+ $html .= '<li><span data-index="'.$j.'" data-href="#scene-'.$s.'-hotspot-'.$j.'"><i class="fa fa-dot-circle-o"></i></span></li>';
1346
  }
1347
  $j++;
1348
  }
1349
+ $html .= '<li class="add" data-repeater-create><span><i class="fa fa-plus-circle"></i></span></li>';
1350
  $html .= '</ul>';
1351
  $html .= '</nav>';
1352
 
1375
  if ($pano_hotspot['hotspot-title'] == $pano_hotspots[0]['hotspot-title']) {
1376
  $html .= '<div data-repeater-item class="single-hotspot rex-pano-tab active clearfix" id="scene-'.$s.'-hotspot-'.$h.'">';
1377
 
1378
+ $html .= '<h6 class="title"><i class="fa fa-cog"></i> Hotspot Setting </h6>';
1379
 
1380
  $html .= '<div class="wrapper">';
1381
  $html .= '<div class="hotspot-setting">';
1384
  $html .= '</div>';
1385
 
1386
  $html .= '<div class="hotspot-setting">';
1387
+ $html .= '<label for="hotspot-pitch">'.__('Pitch: ','wpvr').'</label>';
1388
+ $html .= '<input type="text" class="hotspot-pitch" name="hotspot-pitch" value="'.$hotspot_pitch.'" />';
1389
  $html .= '</div>';
1390
 
1391
  $html .= '<div class="hotspot-setting">';
1392
+ $html .= '<label for="hotspot-yaw">'.__('Yaw: ','wpvr').'</label>';
1393
+ $html .= '<input type="text" class="hotspot-yaw" name="hotspot-yaw" value="'.$hotspot_yaw.'" />';
1394
  $html .= '</div>';
1395
 
1396
  $html .= '<div class="hotspot-setting">';
1424
  $html .= '<textarea name="hotspot-hover">'.$hotspot_hover.'</textarea>';
1425
  $html .= '</div>';
1426
 
1427
+ $html .= '<div class="hotspot-scene" style="display:none;" >';
1428
+ $html .= '<label for="hotspot-scene">'.__('Select Target Scene from List: ','wpvr').'</label>';
1429
+ $html .= '<select class="hotspotscene" name="hotspot-scene-list">';
1430
+ $html .= '<option value="none" selected> None</option>';
1431
+ $html .= '</select>';
1432
+ $html .= '</div>';
1433
+
1434
  $html .= '<div class="hotspot-scene" style="display:none;" >';
1435
  $html .= '<label for="hotspot-scene">'.__('Target Scene ID: ','wpvr').' </label>';
1436
+ $html .= '<input class="hotspotsceneinfodata" type="text" name="hotspot-scene"/>';
1437
  $html .= '</div>';
1438
 
1439
  $html .= '</div>';
1443
 
1444
  $html .= '<div class="hotspot-type hotspot-setting">';
1445
  $html .= '<label for="hotspot-type">'.__('Hotspot-Type: ','wpvr').'</label>';
1446
+ $html .= '<select class="trtr" name="hotspot-type">';
1447
  $html .= '<option value="info"> Info</option>';
1448
  $html .= '<option value="scene" selected> Scene</option>';
1449
  $html .= '</select>';
1453
  $html .= '<input type="url" name="hotspot-url" />';
1454
  $html .= '</div>';
1455
 
1456
+ $html .= '<div class="hotspot-content" style="display:none;">';
1457
  $html .= '<label for="hotspot-content">'.__('On click Content: ','wpvr').'</label>';
1458
  $html .= '<textarea name="hotspot-content"></textarea>';
1459
  $html .= '</div>';
1463
  $html .= '<textarea name="hotspot-hover">'.$hotspot_hover.'</textarea>';
1464
  $html .= '</div>';
1465
 
1466
+ $html .= '<div class="hotspot-scene" >';
1467
+ $html .= '<label for="hotspot-scene">'.__('Select Target Scene from List: ','wpvr').'</label>';
1468
+ $html .= '<select class="hotspotscene" name="hotspot-scene-list">';
1469
+ $html .= '<option value="none" selected> None</option>';
1470
+ $html .= '</select>';
1471
+ $html .= '</div>';
1472
+
1473
  $html .= '<div class="hotspot-scene">';
1474
  $html .= '<label for="hotspot-scene">'.__('Target Scene ID: ','wpvr').'</label>';
1475
+ $html .= '<input class="hotspotsceneinfodata" type="text" name="hotspot-scene" value="'.$hotspot_target_scene.'" disabled />';
1476
  $html .= '</div>';
1477
 
1478
  $html .= '</div>';
1485
  else {
1486
  $html .= '<div data-repeater-item class="single-hotspot rex-pano-tab clearfix" id="scene-'.$s.'-hotspot-'.$h.'">';
1487
 
1488
+ $html .= '<h6 class="title"><i class="fa fa-cog"></i> Hotspot Setting</h6>';
1489
 
1490
  $html .= '<div class="wrapper">';
1491
  $html .= '<div class="hotspot-setting">';
1494
  $html .= '</div>';
1495
 
1496
  $html .= '<div class="hotspot-setting">';
1497
+ $html .= '<label for="hotspot-pitch">'.__('Pitch: ','wpvr').'</label>';
1498
+ $html .= '<input type="text" class="hotspot-pitch" name="hotspot-pitch" value="'.$hotspot_pitch.'" />';
1499
  $html .= '</div>';
1500
 
1501
  $html .= '<div class="hotspot-setting">';
1502
+ $html .= '<label for="hotspot-yaw">'.__('Yaw: ','wpvr').'</label>';
1503
+ $html .= '<input type="text" class="hotspot-yaw" name="hotspot-yaw" value="'.$hotspot_yaw.'" />';
1504
  $html .= '</div>';
1505
 
1506
  $html .= '<div class="hotspot-setting">';
1534
  $html .= '<textarea name="hotspot-hover">'.$hotspot_hover.'</textarea>';
1535
  $html .= '</div>';
1536
 
1537
+ $html .= '<div class="hotspot-scene" style="display:none;" >';
1538
+ $html .= '<label for="hotspot-scene">'.__('Select Target Scene from List: ','wpvr').'</label>';
1539
+ $html .= '<select class="hotspotscene" name="hotspot-scene-list">';
1540
+ $html .= '<option value="none" selected> None</option>';
1541
+ $html .= '</select>';
1542
+ $html .= '</div>';
1543
+
1544
  $html .= '<div class="hotspot-scene" style="display:none;" >';
1545
  $html .= '<label for="hotspot-scene">'.__('Target Scene ID: ','wpvr').' </label>';
1546
+ $html .= '<input class="hotspotsceneinfodata" type="text" name="hotspot-scene" disabled />';
1547
  $html .= '</div>';
1548
 
1549
  $html .= '</div>';
1553
 
1554
  $html .= '<div class="hotspot-type hotspot-setting">';
1555
  $html .= '<label for="hotspot-type">'.__('Hotspot-Type: ','wpvr').'</label>';
1556
+ $html .= '<select class="trtr" name="hotspot-type">';
1557
  $html .= '<option value="info"> Info</option>';
1558
  $html .= '<option value="scene" selected> Scene</option>';
1559
  $html .= '</select>';
1563
  $html .= '<input type="url" name="hotspot-url" />';
1564
  $html .= '</div>';
1565
 
1566
+ $html .= '<div class="hotspot-content" style="display:none;">';
1567
  $html .= '<label for="hotspot-content">'.__('On click Content: ','wpvr').' </label>';
1568
  $html .= '<textarea name="hotspot-content"></textarea>';
1569
  $html .= '</div>';
1573
  $html .= '<textarea name="hotspot-hover">'.$hotspot_hover.'</textarea>';
1574
  $html .= '</div>';
1575
 
1576
+ $html .= '<div class="hotspot-scene" >';
1577
+ $html .= '<label for="hotspot-scene">'.__('Select Target Scene from List: ','wpvr').'</label>';
1578
+ $html .= '<select class="hotspotscene" name="hotspot-scene-list">';
1579
+ $html .= '<option value="none" selected> None</option>';
1580
+ $html .= '</select>';
1581
+ $html .= '</div>';
1582
+
1583
  $html .= '<div class="hotspot-scene">';
1584
  $html .= '<label for="hotspot-scene">'.__('Target Scene ID: ','wpvr').' </label>';
1585
+ $html .= '<input class="hotspotsceneinfodata" type="text" name="hotspot-scene" value="'.$hotspot_target_scene.'" disabled />';
1586
  $html .= '</div>';
1587
 
1588
  $html .= '</div>';
1597
  $html .= '</div>';
1598
  $html .= '</div>';
1599
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1600
  $html .= '<button data-repeater-delete type="button" title="Delete Scene" class="delete-scene"><i class="fa fa-trash-o"></i></button>';
1601
  $html .= '</div>';
1602
  }
1609
 
1610
  $html .= '<div class="preview-btn-wrapper">';
1611
  $html .= '<div class="preview-btn-area clearfix">';
1612
+
1613
  $html .= '<button id="panolenspreview">'.__('Preview','wpvr').'</button>';
1614
  $html .= '</div>';
1615
  $html .= '</div>';
1616
  $html .='</div>';
1617
  //---end scenes tab----
1618
+ $html .= '<div id="error_occured"></div>';
1619
+ //----start video tab content---------
1620
+ $html .='<div class="rex-pano-tab video" id="video">';
1621
+ $html .= '<h6 class="title"> '.__('Video Settings : ','wpvr').'</h6>';
1622
+
1623
+ //==Video Setup==//
1624
+ // if( $status !== false && $status == 'valid' ) {
1625
+ if (isset($postdata['vidid'])) {
1626
+ $vidautoplay = $postdata['vidautoplay'];
1627
+ $vidautoplay_on = '';
1628
+ $vidautoplay_off = '';
1629
+ if (!empty($vidautoplay)) {
1630
+ $vidautoplay_on = 'checked';
1631
+ }
1632
+ else {
1633
+ $vidautoplay_off = 'checked';
1634
+ }
1635
+
1636
+ $vidcontrol = $postdata['vidcontrol'];
1637
+ $vidcontrol_on = '';
1638
+ $vidcontrol_off = '';
1639
+ if (!empty($vidcontrol)) {
1640
+ $vidcontrol_on = 'checked';
1641
+ }
1642
+ else {
1643
+ $vidcontrol_off = 'checked';
1644
+ }
1645
+ $html .= '<div class="single-settings videosetup">';
1646
+ $html .= '<span>Enable Video: </span>';
1647
+ $html .= '<ul>';
1648
+ $html .= '<li class="radio-btn">';
1649
+ $html .= '<input class="styled-radio" id="styled-radio" type="radio" name="panovideo" value="off" >';
1650
+ $html .= '<label for="styled-radio">Off</label>';
1651
+ $html .= '</li>';
1652
+
1653
+ $html .= '<li class="radio-btn">';
1654
+ $html .= '<input class="styled-radio" id="styled-radio-0" type="radio" name="panovideo" value="on" checked>';
1655
+ $html .= '<label for="styled-radio-0">On</label>';
1656
+ $html .= '</li>';
1657
+ $html .= '</ul>';
1658
+ $html .= '</div>';
1659
+
1660
+
1661
+ $html .= '<div class="video-setting" style="display:none;">';
1662
+ $html .= '<div class="single-settings">';
1663
+ $html .= '<span>Upload or add link: </span>';
1664
+ $html .= '<div class="form-group">';
1665
+ $html .= '<input type="text" name="video-attachment-url" placeholder="Paste Youtube or Vimeo link or upload" class="video-attachment-url" value="'.$postdata['vidurl'].'">';
1666
+ $html .= '<input type="button" class="video-upload" data-info="" value="Upload" />';
1667
+ $html .= '</div>';
1668
+ $html .= '</div>';
1669
+ $html .= '<button id="videopreview">Preview</button>';
1670
+ $html .= '</div>';
1671
+ }
1672
+ else {
1673
+ $html .= '<div class="single-settings videosetup">';
1674
+ $html .= '<span>Enable Video: </span>';
1675
+ $html .= '<ul>';
1676
+ $html .= '<li class="radio-btn">';
1677
+ $html .= '<input class="styled-radio" id="styled-radio" type="radio" name="panovideo" value="off" checked >';
1678
+ $html .= '<label for="styled-radio">Off</label>';
1679
+ $html .= '</li>';
1680
+
1681
+ $html .= '<li class="radio-btn">';
1682
+ $html .= '<input class="styled-radio" id="styled-radio-0" type="radio" name="panovideo" value="on" >';
1683
+ $html .= '<label for="styled-radio-0">On</label>';
1684
+ $html .= '</li>';
1685
+ $html .= '</ul>';
1686
+ $html .= '</div>';
1687
+
1688
+ //==Video setup end==//
1689
+
1690
+ //==Video Setting==/
1691
+ $html .= '<div class="video-setting" style="display:none;">';
1692
+ $html .= '<div class="single-settings">';
1693
+ $html .= '<span>Upload or add link: </span>';
1694
+ $html .= '<div class="form-group">';
1695
+ $html .= '<input type="text" placeholder="Paste Youtube or Vimeo link or upload" name="video-attachment-url" class="video-attachment-url" value="">';
1696
+ $html .= '<input type="button" class="video-upload" data-info="" value="Upload"/>';
1697
+ $html .= '</div>';
1698
+ $html .= '</div>';
1699
+ $html .= '<button id="videopreview">Preview</button>';
1700
+ $html .= '</div>';
1701
+ }
1702
+ // }
1703
+ //==Video Setting End==//
1704
+ $html .='</div>';
1705
+ //---end video tab----
1706
 
1707
  $html .='</div>';
1708
  //---end rex-pano-tab-content----
admin/class-wpvr-ajax.php CHANGED
@@ -40,10 +40,16 @@ class Wpvr_Ajax {
40
 
41
  $default_scene = '';
42
  $default_scene = sanitize_text_field($_POST['defaultscene']);
43
- // if (empty($default_scene)) {
44
- // wp_send_json_error('<p><span>Warning:</span> Default scene id required. Go to general setting\'s tab & check.</p>');
45
- // die();
46
- // }
 
 
 
 
 
 
47
 
48
  $scene_fade_duration = '';
49
  $scene_fade_duration = sanitize_text_field($_POST['scenefadeduration']);
@@ -51,10 +57,6 @@ class Wpvr_Ajax {
51
  $panodata = $_POST['panodata'];
52
 
53
  //===Error Control and Validation===//
54
- // if (!wpvr_in_array_r($default_scene, $panodata)) {
55
- // wp_send_json_error('<p><span>Warning:</span> Default scene id is invalid. Go to general setting\'s tab & check.</p>');
56
- // die();
57
- // }
58
 
59
  if ($panodata["scene-list"] != "") {
60
  foreach ($panodata["scene-list"] as $scenes_val) {
@@ -66,79 +68,73 @@ class Wpvr_Ajax {
66
  wp_send_json_error('<p><span>Warning:</span> The scene id can only contain letters and numbers</p>');
67
  die();
68
  }
69
- }
70
-
71
- if (empty($scene_id_validate)) {
72
- wp_send_json_error('<p><span>Warning:</span> Scene Id is required for every scene.</p>');
73
- die();
74
- }
75
-
76
- if (empty($scenes_val["scene-attachment-url"])) {
77
- wp_send_json_error('<p><span>Warning:</span> A scene image is required for every scene.</p>');
78
- die();
79
- }
80
-
81
- if ($scenes_val["hotspot-list"] != "") {
82
- foreach ($scenes_val["hotspot-list"] as $hotspot_val) {
83
- $hotspot_title_validate = $hotspot_val["hotspot-title"];
84
-
85
- if (!empty($hotspot_title_validate)) {
86
- $hotspot_title_validated = preg_replace('/[^0-9a-zA-Z_]/',"",$hotspot_title_validate);
87
- if ($hotspot_title_validated != $hotspot_title_validate) {
88
- wp_send_json_error('<p><span>Warning:</span> Hotspot title can only contain letters and numbers</p>');
89
- die();
90
- }
91
- }
92
-
93
- $hotspot_pitch_validate = $hotspot_val["hotspot-pitch"];
94
- if (!empty($hotspot_pitch_validate)) {
95
- $hotspot_pitch_validated = preg_replace('/[^0-9.-]/','',$hotspot_pitch_validate);
96
- if ($hotspot_pitch_validated != $hotspot_pitch_validate) {
97
- wp_send_json_error('<p><span>Warning:</span> Hotspot pitch can only contain float numbers</p>');
98
- die();
99
- }
100
- }
101
-
102
- $hotspot_yaw_validate = $hotspot_val["hotspot-yaw"];
103
- if (!empty($hotspot_yaw_validate)) {
104
- $hotspot_yaw_validated = preg_replace('/[^0-9.-]/','',$hotspot_yaw_validate);
105
- if ($hotspot_yaw_validated != $hotspot_yaw_validate) {
106
- wp_send_json_error('<p><span>Warning:</span> Hotspot yaw can only contain float numbers</p>');
107
- die();
108
- }
109
- }
110
- $hotspot_type_validate = $hotspot_val["hotspot-type"];
111
- $hotspot_url_validate = $hotspot_val["hotspot-url"];
112
- if (!empty($hotspot_url_validate)) {
113
- $hotspot_url_validated = esc_url($hotspot_url_validate);
114
- if ($hotspot_url_validated != $hotspot_url_validate) {
115
- wp_send_json_error('<p><span>Warning:</span> Hotspot Url is invalid</p>');
116
- die();
117
- }
118
- }
119
- $hotspot_content_validate = $hotspot_val["hotspot-content"];
120
-
121
- $hotspot_scene_validate = $hotspot_val["hotspot-scene"];
122
-
123
- if ($hotspot_type_validate == "info") {
124
- if (!empty($hotspot_scene_validate)) {
125
- wp_send_json_error('<p><span>Warning:</span> Don\'t add Target Scene ID on info type hotspot</p>');
126
- die();
127
- }
128
- if (!empty($hotspot_url_validate) && !empty($hotspot_content_validate)) {
129
- wp_send_json_error('<p><span>Warning:</span> Don\'t add Url and On click content both on same hotspot.</p>');
130
- die();
131
- }
132
- }
133
 
134
- if ($hotspot_type_validate == "scene") {
135
- if (empty($hotspot_scene_validate)) {
136
- wp_send_json_error('<p><span>Warning:</span> Target scene id is required for scene type hotspot.</p>');
137
- die();
138
- }
139
- if (!empty($hotspot_url_validate) || !empty($hotspot_content_validate)) {
140
- wp_send_json_error('<p><span>Warning:</span> Don\'t add Url or On click content on scene type hotspot.</p>');
141
- die();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
142
  }
143
  }
144
  }
@@ -160,44 +156,135 @@ class Wpvr_Ajax {
160
  $default_data = array();
161
  $default_data = array(__( "firstScene" )=>$default_scene,__( "sceneFadeDuration" )=>$scene_fade_duration);
162
  $scene_data = array();
163
-
 
164
  foreach ($panodata["scene-list"] as $panoscenes) {
165
 
166
- $hotspot_datas = $panoscenes["hotspot-list"];
167
- $hotspots = array();
168
- foreach ($hotspot_datas as $hotspot_data) {
169
-
170
- $hotspot_info = array(
171
- __( "text" )=>$hotspot_data["hotspot-title"],
172
- __( "pitch" )=>$hotspot_data["hotspot-pitch"],
173
- __( "yaw" )=>$hotspot_data["hotspot-yaw"],
174
- __( "type" )=>$hotspot_data["hotspot-type"],
175
- __( "URL" )=>$hotspot_data["hotspot-url"],
176
- __( "clickHandlerArgs" )=>$hotspot_data["hotspot-content"],
177
- __( "createTooltipArgs" )=>$hotspot_data["hotspot-hover"],
178
- __( "sceneId" )=>$hotspot_data["hotspot-scene"]);
179
- array_push($hotspots, $hotspot_info);
180
- }
 
 
 
181
 
182
- $scene_info = array();
183
- $scene_info = array(__( "type" )=>$panoscenes["scene-type"],__( "panorama" )=>$panoscenes["scene-attachment-url"],__( "hotSpots" )=>$hotspots);
184
- $scene_array = array();
185
- $scene_array = array(
186
- __($panoscenes["scene-id"])=>$scene_info
187
- );
188
- $scene_data[$panoscenes["scene-id"]] = $scene_info;
 
189
  }
190
 
191
  $pano_id_array = array();
192
  $pano_id_array = array(__( "panoid" )=>$panoid);
193
  $pano_response = array();
194
- $pano_response = array(__( "autoLoad" )=>$autoload,__( "showControls" )=>$control,__( "default" )=>$default_data,__( "scenes" )=>$scene_data);
 
 
 
 
 
 
 
 
 
 
 
 
195
  $response = array();
196
  $response = array($pano_id_array,$pano_response);
197
 
198
  wp_send_json_success( $response );
199
  }
200
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
201
  function wpvr_save_data() {
202
  $panoid ='';
203
  $postid = sanitize_text_field($_POST['postid']);
@@ -207,6 +294,78 @@ class Wpvr_Ajax {
207
  }
208
  $panoid = 'pano'.$postid;
209
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
210
  $control = sanitize_text_field($_POST['control']);
211
  if ($control == 'on') {
212
  $control = true;
@@ -225,16 +384,22 @@ class Wpvr_Ajax {
225
 
226
  $default_scene = '';
227
 
 
 
 
 
 
 
 
 
 
 
228
  $scene_fade_duration = '';
229
  $scene_fade_duration = $_POST['scenefadeduration'];
230
 
231
  $panodata = $_POST['panodata'];
232
 
233
  //===Error Control and Validation===//
234
- // if (!wpvr_in_array_r($default_scene, $panodata)) {
235
- // wp_send_json_error('<p><span>Warning:</span> Default scene id is invalid. Go to general setting\'s tab & check.</p>');
236
- // die();
237
- // }
238
 
239
  if ($panodata["scene-list"] != "") {
240
  foreach ($panodata["scene-list"] as $scenes_val) {
@@ -245,91 +410,85 @@ class Wpvr_Ajax {
245
  wp_send_json_error('<p><span>Warning:</span> The scene id can only contain letters and numbers.</p>');
246
  die();
247
  }
248
- }
249
-
250
- if (empty($scene_id_validate)) {
251
- wp_send_json_error('<p>Scene Id is required</p>');
252
- die();
253
- }
254
-
255
- if (empty($scenes_val["scene-attachment-url"])) {
256
- wp_send_json_error('<p><span>Warning:</span> A scene image is required for every scene.</p>');
257
- die();
258
- }
259
-
260
- if ($scenes_val["hotspot-list"] != "") {
261
- foreach ($scenes_val["hotspot-list"] as $hotspot_val) {
262
- $hotspot_title_validate = $hotspot_val["hotspot-title"];
263
- if (empty($hotspot_title_validate)) {
264
- wp_send_json_error('<p><span>Warning:</span> Hotspot title is required for every hotspot.</p>');
265
- die();
266
- }
267
-
268
- if (!empty($hotspot_title_validate)) {
269
- $hotspot_title_validated = preg_replace('/[^0-9a-zA-Z_]/',"",$hotspot_title_validate);
270
- if ($hotspot_title_validated != $hotspot_title_validate) {
271
- wp_send_json_error('<p><span>Warning:</span> Hotspot title can only contain letters and numbers.</p>');
272
- die();
273
- }
274
- }
275
-
276
- $hotspot_pitch_validate = $hotspot_val["hotspot-pitch"];
277
- if (empty($hotspot_pitch_validate)) {
278
- wp_send_json_error('<p><span>Warning:</span> Hotspot pitch is required for every hotspot.</p>');
279
- die();
280
- }
281
- if (!empty($hotspot_pitch_validate)) {
282
- $hotspot_pitch_validated = preg_replace('/[^0-9.-]/','',$hotspot_pitch_validate);
283
- if ($hotspot_pitch_validated != $hotspot_pitch_validate) {
284
- wp_send_json_error('<p><span>Warning:</span> Hotspot pitch can only contain float numbers.</p>');
285
- die();
286
- }
287
- }
288
 
289
- $hotspot_yaw_validate = $hotspot_val["hotspot-yaw"];
290
- if (empty($hotspot_yaw_validate)) {
291
- wp_send_json_error('<p><span>Warning:</span> Hotspot yaw is required for every hotspot.</p>');
292
- die();
293
- }
294
- if (!empty($hotspot_yaw_validate)) {
295
- $hotspot_yaw_validated = preg_replace('/[^0-9.-]/','',$hotspot_yaw_validate);
296
- if ($hotspot_yaw_validated != $hotspot_yaw_validate) {
297
- wp_send_json_error('<p><span>Warning:</span> Hotspot yaw can only contain float numbers.</p>');
298
- die();
299
- }
300
- }
301
- $hotspot_type_validate = $hotspot_val["hotspot-type"];
302
- $hotspot_url_validate = $hotspot_val["hotspot-url"];
303
- if (!empty($hotspot_url_validate)) {
304
- $hotspot_url_validated = esc_url($hotspot_url_validate);
305
- if ($hotspot_url_validated != $hotspot_url_validate) {
306
- wp_send_json_error('<p><span>Warning:</span> Hotspot Url is invalid.</p>');
307
- die();
308
- }
309
- }
310
- $hotspot_content_validate = $hotspot_val["hotspot-content"];
311
-
312
- $hotspot_scene_validate = $hotspot_val["hotspot-scene"];
313
-
314
- if ($hotspot_type_validate == "info") {
315
- if (!empty($hotspot_scene_validate)) {
316
- wp_send_json_error('<p><span>Warning:</span> Don\'t add Target Scene ID on info type hotspot.</p>');
317
- die();
318
- }
319
- if (!empty($hotspot_url_validate) && !empty($hotspot_content_validate)) {
320
- wp_send_json_error('<p><span>Warning:</span> Don\'t add Url and On click content both on same hotspot.</p>');
321
- die();
322
- }
323
- }
324
 
325
- if ($hotspot_type_validate == "scene") {
326
- if (empty($hotspot_scene_validate)) {
327
- wp_send_json_error('<p><span>Warning:</span> Targer scene id is required for scene type hotspot.</p>');
328
- die();
329
- }
330
- if (!empty($hotspot_url_validate) || !empty($hotspot_content_validate)) {
331
- wp_send_json_error('<p><span>Warning:</span> Don\'t add Url or On click content on scene type hotspot.</p>');
332
- die();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
333
  }
334
  }
335
  }
@@ -346,8 +505,40 @@ class Wpvr_Ajax {
346
  wp_send_json_error('<p><span>Warning:</span> Default scene is required. Set a scene as deafualt to load it by deafult.</p>');
347
  die();
348
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
349
  $pano_array = array();
350
- $pano_array = array(__( "panoid" )=>$panoid,__( "autoLoad" )=>$autoload,__( "showControls" )=>$control,__( "defaultscene" )=>$default_scene,__( "scenefadeduration" )=>$scene_fade_duration,__( "panodata" )=>$panodata);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
351
  update_post_meta( $postid, 'panodata', $pano_array );
352
  die();
353
  }
40
 
41
  $default_scene = '';
42
  $default_scene = sanitize_text_field($_POST['defaultscene']);
43
+
44
+ $preview = '';
45
+ $preview = esc_url($_POST['preview']);
46
+
47
+ $autorotation = '';
48
+ $autorotation = sanitize_text_field($_POST['autorotation']);
49
+ $autorotationinactivedelay = '';
50
+ $autorotationinactivedelay = sanitize_text_field($_POST['autorotationinactivedelay']);
51
+ $autorotationstopdelay = '';
52
+ $autorotationstopdelay = sanitize_text_field($_POST['autorotationstopdelay']);
53
 
54
  $scene_fade_duration = '';
55
  $scene_fade_duration = sanitize_text_field($_POST['scenefadeduration']);
57
  $panodata = $_POST['panodata'];
58
 
59
  //===Error Control and Validation===//
 
 
 
 
60
 
61
  if ($panodata["scene-list"] != "") {
62
  foreach ($panodata["scene-list"] as $scenes_val) {
68
  wp_send_json_error('<p><span>Warning:</span> The scene id can only contain letters and numbers</p>');
69
  die();
70
  }
71
+ if (empty($scenes_val["scene-attachment-url"])) {
72
+ wp_send_json_error('<p><span>Warning:</span> A scene image is required for every scene.</p>');
73
+ die();
74
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
75
 
76
+ if ($scenes_val["hotspot-list"] != "") {
77
+ foreach ($scenes_val["hotspot-list"] as $hotspot_val) {
78
+ $hotspot_title_validate = $hotspot_val["hotspot-title"];
79
+
80
+ if (!empty($hotspot_title_validate)) {
81
+ $hotspot_title_validated = preg_replace('/[^0-9a-zA-Z_]/',"",$hotspot_title_validate);
82
+ if ($hotspot_title_validated != $hotspot_title_validate) {
83
+ wp_send_json_error('<p><span>Warning:</span> Hotspot title can only contain letters and numbers</p>');
84
+ die();
85
+ }
86
+
87
+ $hotspot_pitch_validate = $hotspot_val["hotspot-pitch"];
88
+ if (!empty($hotspot_pitch_validate)) {
89
+ $hotspot_pitch_validated = preg_replace('/[^0-9.-]/','',$hotspot_pitch_validate);
90
+ if ($hotspot_pitch_validated != $hotspot_pitch_validate) {
91
+ wp_send_json_error('<p><span>Warning:</span> Hotspot pitch can only contain float numbers</p>');
92
+ die();
93
+ }
94
+ }
95
+
96
+ $hotspot_yaw_validate = $hotspot_val["hotspot-yaw"];
97
+ if (!empty($hotspot_yaw_validate)) {
98
+ $hotspot_yaw_validated = preg_replace('/[^0-9.-]/','',$hotspot_yaw_validate);
99
+ if ($hotspot_yaw_validated != $hotspot_yaw_validate) {
100
+ wp_send_json_error('<p><span>Warning:</span> Hotspot yaw can only contain float numbers</p>');
101
+ die();
102
+ }
103
+ }
104
+ $hotspot_type_validate = $hotspot_val["hotspot-type"];
105
+ $hotspot_url_validate = $hotspot_val["hotspot-url"];
106
+ if (!empty($hotspot_url_validate)) {
107
+ $hotspot_url_validated = esc_url($hotspot_url_validate);
108
+ if ($hotspot_url_validated != $hotspot_url_validate) {
109
+ wp_send_json_error('<p><span>Warning:</span> Hotspot Url is invalid</p>');
110
+ die();
111
+ }
112
+ }
113
+ $hotspot_content_validate = $hotspot_val["hotspot-content"];
114
+
115
+ $hotspot_scene_validate = $hotspot_val["hotspot-scene"];
116
+
117
+ if ($hotspot_type_validate == "info") {
118
+ if (!empty($hotspot_scene_validate)) {
119
+ wp_send_json_error('<p><span>Warning:</span> Don\'t add Target Scene ID on info type hotspot</p>');
120
+ die();
121
+ }
122
+ if (!empty($hotspot_url_validate) && !empty($hotspot_content_validate)) {
123
+ wp_send_json_error('<p><span>Warning:</span> Don\'t add Url and On click content both on same hotspot.</p>');
124
+ die();
125
+ }
126
+ }
127
+
128
+ if ($hotspot_type_validate == "scene") {
129
+ if (empty($hotspot_scene_validate)) {
130
+ wp_send_json_error('<p><span>Warning:</span> Target scene id is required for scene type hotspot.</p>');
131
+ die();
132
+ }
133
+ if (!empty($hotspot_url_validate) || !empty($hotspot_content_validate)) {
134
+ wp_send_json_error('<p><span>Warning:</span> Don\'t add Url or On click content on scene type hotspot.</p>');
135
+ die();
136
+ }
137
+ }
138
  }
139
  }
140
  }
156
  $default_data = array();
157
  $default_data = array(__( "firstScene" )=>$default_scene,__( "sceneFadeDuration" )=>$scene_fade_duration);
158
  $scene_data = array();
159
+ // var_dump($panodata["scene-list"]);
160
+ // die();
161
  foreach ($panodata["scene-list"] as $panoscenes) {
162
 
163
+ if (!empty($panoscenes['scene-id'])) {
164
+ $hotspot_datas = $panoscenes["hotspot-list"];
165
+ $hotspots = array();
166
+ foreach ($hotspot_datas as $hotspot_data) {
167
+
168
+ if (!empty($hotspot_data["hotspot-title"])) {
169
+ $hotspot_info = array(
170
+ __( "text" )=>$hotspot_data["hotspot-title"],
171
+ __( "pitch" )=>$hotspot_data["hotspot-pitch"],
172
+ __( "yaw" )=>$hotspot_data["hotspot-yaw"],
173
+ __( "type" )=>$hotspot_data["hotspot-type"],
174
+ __( "URL" )=>$hotspot_data["hotspot-url"],
175
+ __( "clickHandlerArgs" )=>$hotspot_data["hotspot-content"],
176
+ __( "createTooltipArgs" )=>$hotspot_data["hotspot-hover"],
177
+ __( "sceneId" )=>$hotspot_data["hotspot-scene"]);
178
+ array_push($hotspots, $hotspot_info);
179
+ }
180
+ }
181
 
182
+ $scene_info = array();
183
+ $scene_info = array(__( "type" )=>$panoscenes["scene-type"],__( "panorama" )=>$panoscenes["scene-attachment-url"],__( "hotSpots" )=>$hotspots);
184
+ $scene_array = array();
185
+ $scene_array = array(
186
+ __($panoscenes["scene-id"])=>$scene_info
187
+ );
188
+ $scene_data[$panoscenes["scene-id"]] = $scene_info;
189
+ }
190
  }
191
 
192
  $pano_id_array = array();
193
  $pano_id_array = array(__( "panoid" )=>$panoid);
194
  $pano_response = array();
195
+ $pano_response = array(__( "autoLoad" )=>$autoload,__( "showControls" )=>$control,__( "preview" )=>$preview,__( "autoRotate" )=>$autorotation,__( "autoRotateInactivityDelay" )=>$autorotationinactivedelay,__( "autoRotateStopDelay" )=>$autorotationstopdelay,__( "default" )=>$default_data,__( "scenes" )=>$scene_data);
196
+
197
+ if (empty($autorotation)) {
198
+ unset($pano_response['autoRotate']);
199
+ unset($pano_response['autoRotateInactivityDelay']);
200
+ unset($pano_response['autoRotateStopDelay']);
201
+ }
202
+ if (empty($autorotationinactivedelay)) {
203
+ unset($pano_response['autoRotateInactivityDelay']);
204
+ }
205
+ if (empty($autorotationstopdelay)) {
206
+ unset($pano_response['autoRotateStopDelay']);
207
+ }
208
  $response = array();
209
  $response = array($pano_id_array,$pano_response);
210
 
211
  wp_send_json_success( $response );
212
  }
213
 
214
+ /**
215
+ * Video Preview show ajax function
216
+ */
217
+ function wpvrvideo_preview() {
218
+ $panoid ='';
219
+ $postid = sanitize_text_field($_POST['postid']);
220
+ $panoid = 'pano'.$postid;
221
+ $randid = rand(1000, 1000000);
222
+ $vidid = 'vid'.$randid;
223
+ $videourl = sanitize_url($_POST['videourl']);
224
+
225
+ $vidtype = '';
226
+ if (strpos($videourl, 'youtube') > 0) {
227
+ $vidtype = 'youtube';
228
+ $explodeid = '';
229
+ $explodeid = explode("=",$videourl);
230
+ $foundid = '';
231
+ $foundid = $explodeid[1];
232
+ $html = '';
233
+ $html .= '<iframe width="600" height="400" src="https://www.youtube.com/embed/'.$foundid.'" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>';
234
+
235
+ } elseif (strpos($videourl, 'youtu.be') > 0) {
236
+ $vidtype = 'youtube';
237
+ $explodeid = '';
238
+ $explodeid = explode("/",$videourl);
239
+ $foundid = '';
240
+ $foundid = $explodeid[3];
241
+ $html = '';
242
+ $html .= '<iframe width="600" height="400" src="https://www.youtube.com/embed/'.$foundid.'" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>';
243
+
244
+ }
245
+ elseif (strpos($videourl, 'vimeo') > 0) {
246
+ $vidtype = 'vimeo';
247
+ $explodeid = '';
248
+ $explodeid = explode("/",$videourl);
249
+ $foundid = '';
250
+ $foundid = $explodeid[3];
251
+ $html = '';
252
+ $html .= '<iframe src="https://player.vimeo.com/video/'.$foundid.'" width="600" height="400" frameborder="0" webkitallowfullscreen mozallowfullscreen allowfullscreen></iframe>';
253
+
254
+ } else {
255
+ $vidtype = 'selfhost';
256
+ $vidautoplay = '';
257
+ $vidautoplay = sanitize_text_field($_POST['vidautoplay']);
258
+ if ($vidautoplay == 'on') {
259
+ $vidautoplay = 'autoplay';
260
+ }
261
+ else {
262
+ $vidautoplay = '';
263
+ }
264
+
265
+ $vidcontrol = '';
266
+ $vidcontrol = sanitize_text_field($_POST['vidcontrol']);
267
+ if ($vidcontrol == 'on') {
268
+ $vidcontrol = 'controls';
269
+ }
270
+ else {
271
+ $vidcontrol = '';
272
+ }
273
+
274
+ $html = '';
275
+ $html .= '<video id="'.$vidid.'" class="video-js vjs-default-skin vjs-big-play-centered" controls preload="none" style="width:100%;height:400px;" poster="" >';
276
+ $html .= '<source src="'.$videourl.'" type="video/mp4"/>';
277
+ $html .= '<p class="vjs-no-js">';
278
+ $html .= 'To view this video please enable JavaScript, and consider upgrading to a web browser that <a href="http://videojs.com html5-video-support/" target="_blank">supports HTML5 video</a>';
279
+ $html .= '</p>';
280
+ $html .= '</video>';
281
+ }
282
+
283
+ $response = array();
284
+ $response = array(__( "panoid" )=>$panoid,__( "panodata" )=>$html,__( "vidid" )=>$vidid,__( "vidtype" )=>$vidtype);
285
+ wp_send_json_success( $response );
286
+ }
287
+
288
  function wpvr_save_data() {
289
  $panoid ='';
290
  $postid = sanitize_text_field($_POST['postid']);
294
  }
295
  $panoid = 'pano'.$postid;
296
 
297
+ $pnovideo = $_POST['panovideo'];
298
+ if ($pnovideo == "on") {
299
+
300
+
301
+ $vidid = 'vid'.$postid;
302
+ $videourl = sanitize_url($_POST['videourl']);
303
+ $vidtype = '';
304
+ if (strpos($videourl, 'youtube') > 0) {
305
+ $vidtype = 'youtube';
306
+ $explodeid = '';
307
+ $explodeid = explode("=",$videourl);
308
+ $foundid = '';
309
+ $foundid = $explodeid[1];
310
+ $html = '';
311
+ $html .= '<iframe width="600" height="400" src="https://www.youtube.com/embed/'.$foundid.'" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>';
312
+
313
+ } elseif (strpos($videourl, 'youtu.be') > 0) {
314
+ $vidtype = 'youtube';
315
+ $explodeid = '';
316
+ $explodeid = explode("/",$videourl);
317
+ $foundid = '';
318
+ $foundid = $explodeid[3];
319
+ $html = '';
320
+ $html .= '<iframe width="600" height="400" src="https://www.youtube.com/embed/'.$foundid.'" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>';
321
+
322
+ }
323
+
324
+ elseif (strpos($videourl, 'vimeo') > 0) {
325
+ $vidtype = 'vimeo';
326
+ $explodeid = '';
327
+ $explodeid = explode("/",$videourl);
328
+ $foundid = '';
329
+ $foundid = $explodeid[3];
330
+ $html = '';
331
+ $html .= '<iframe src="https://player.vimeo.com/video/'.$foundid.'" width="600" height="400" frameborder="0" webkitallowfullscreen mozallowfullscreen allowfullscreen></iframe>';
332
+
333
+ } else {
334
+ $vidtype = 'selfhost';
335
+ $vidautoplay = '';
336
+ $vidautoplay = sanitize_text_field($_POST['vidautoplay']);
337
+ if ($vidautoplay == 'on') {
338
+ $vidautoplay = 'autoplay';
339
+ }
340
+ else {
341
+ $vidautoplay = '';
342
+ }
343
+
344
+ $vidcontrol = '';
345
+ $vidcontrol = sanitize_text_field($_POST['vidcontrol']);
346
+ if ($vidcontrol == 'on') {
347
+ $vidcontrol = 'controls';
348
+ }
349
+ else {
350
+ $vidcontrol = '';
351
+ }
352
+
353
+ $html = '';
354
+ $html .= '<video id="'.$vidid.'" class="video-js vjs-default-skin vjs-big-play-centered" controls preload="none" style="width:100%;height:100%;" poster="" >';
355
+ $html .= '<source src="'.$videourl.'" type="video/mp4"/>';
356
+ $html .= '<p class="vjs-no-js">';
357
+ $html .= 'To view this video please enable JavaScript, and consider upgrading to a web browser that <a href="http://videojs.com html5-video-support/" target="_blank">supports HTML5 video</a>';
358
+ $html .= '</p>';
359
+ $html .= '</video>';
360
+ }
361
+
362
+ $videoarray = array();
363
+ $videoarray = array(__( "panoid" )=>$panoid,__( "panoviddata" )=>$html,__( "vidid" )=>$vidid,__( "vidurl" )=>$videourl,__( "vidautoplay" )=>$vidautoplay,__( "vidcontrol" )=>$vidcontrol,__( "vidtype" )=>$vidtype);
364
+ update_post_meta( $postid, 'panodata', $videoarray );
365
+ die();
366
+ }
367
+
368
+
369
  $control = sanitize_text_field($_POST['control']);
370
  if ($control == 'on') {
371
  $control = true;
384
 
385
  $default_scene = '';
386
 
387
+ $preview = '';
388
+ $preview = esc_url($_POST['preview']);
389
+
390
+ $autorotation = '';
391
+ $autorotation = sanitize_text_field($_POST['autorotation']);
392
+ $autorotationinactivedelay = '';
393
+ $autorotationinactivedelay = sanitize_text_field($_POST['autorotationinactivedelay']);
394
+ $autorotationstopdelay = '';
395
+ $autorotationstopdelay = sanitize_text_field($_POST['autorotationstopdelay']);
396
+
397
  $scene_fade_duration = '';
398
  $scene_fade_duration = $_POST['scenefadeduration'];
399
 
400
  $panodata = $_POST['panodata'];
401
 
402
  //===Error Control and Validation===//
 
 
 
 
403
 
404
  if ($panodata["scene-list"] != "") {
405
  foreach ($panodata["scene-list"] as $scenes_val) {
410
  wp_send_json_error('<p><span>Warning:</span> The scene id can only contain letters and numbers.</p>');
411
  die();
412
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
413
 
414
+ if (empty($scenes_val["scene-attachment-url"])) {
415
+ wp_send_json_error('<p><span>Warning:</span> A scene image is required for every scene.</p>');
416
+ die();
417
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
418
 
419
+ if ($scenes_val["hotspot-list"] != "") {
420
+ foreach ($scenes_val["hotspot-list"] as $hotspot_val) {
421
+ $hotspot_title_validate = $hotspot_val["hotspot-title"];
422
+ // if (empty($hotspot_title_validate)) {
423
+ // wp_send_json_error('<p><span>Warning:</span> Hotspot title is required for every hotspot.</p>');
424
+ // die();
425
+ // }
426
+
427
+ if (!empty($hotspot_title_validate)) {
428
+ $hotspot_title_validated = preg_replace('/[^0-9a-zA-Z_]/',"",$hotspot_title_validate);
429
+ if ($hotspot_title_validated != $hotspot_title_validate) {
430
+ wp_send_json_error('<p><span>Warning:</span> Hotspot title can only contain letters and numbers.</p>');
431
+ die();
432
+ }
433
+ $hotspot_pitch_validate = $hotspot_val["hotspot-pitch"];
434
+ if (empty($hotspot_pitch_validate)) {
435
+ wp_send_json_error('<p><span>Warning:</span> Hotspot pitch is required for every hotspot.</p>');
436
+ die();
437
+ }
438
+ if (!empty($hotspot_pitch_validate)) {
439
+ $hotspot_pitch_validated = preg_replace('/[^0-9.-]/','',$hotspot_pitch_validate);
440
+ if ($hotspot_pitch_validated != $hotspot_pitch_validate) {
441
+ wp_send_json_error('<p><span>Warning:</span> Hotspot pitch can only contain float numbers.</p>');
442
+ die();
443
+ }
444
+ }
445
+
446
+ $hotspot_yaw_validate = $hotspot_val["hotspot-yaw"];
447
+ if (empty($hotspot_yaw_validate)) {
448
+ wp_send_json_error('<p><span>Warning:</span> Hotspot yaw is required for every hotspot.</p>');
449
+ die();
450
+ }
451
+ if (!empty($hotspot_yaw_validate)) {
452
+ $hotspot_yaw_validated = preg_replace('/[^0-9.-]/','',$hotspot_yaw_validate);
453
+ if ($hotspot_yaw_validated != $hotspot_yaw_validate) {
454
+ wp_send_json_error('<p><span>Warning:</span> Hotspot yaw can only contain float numbers.</p>');
455
+ die();
456
+ }
457
+ }
458
+ $hotspot_type_validate = $hotspot_val["hotspot-type"];
459
+ $hotspot_url_validate = $hotspot_val["hotspot-url"];
460
+ if (!empty($hotspot_url_validate)) {
461
+ $hotspot_url_validated = esc_url($hotspot_url_validate);
462
+ if ($hotspot_url_validated != $hotspot_url_validate) {
463
+ wp_send_json_error('<p><span>Warning:</span> Hotspot Url is invalid.</p>');
464
+ die();
465
+ }
466
+ }
467
+ $hotspot_content_validate = $hotspot_val["hotspot-content"];
468
+
469
+ $hotspot_scene_validate = $hotspot_val["hotspot-scene"];
470
+
471
+ if ($hotspot_type_validate == "info") {
472
+ if (!empty($hotspot_scene_validate)) {
473
+ wp_send_json_error('<p><span>Warning:</span> Don\'t add Target Scene ID on info type hotspot.</p>');
474
+ die();
475
+ }
476
+ if (!empty($hotspot_url_validate) && !empty($hotspot_content_validate)) {
477
+ wp_send_json_error('<p><span>Warning:</span> Don\'t add Url and On click content both on same hotspot.</p>');
478
+ die();
479
+ }
480
+ }
481
+
482
+ if ($hotspot_type_validate == "scene") {
483
+ if (empty($hotspot_scene_validate)) {
484
+ wp_send_json_error('<p><span>Warning:</span> Target scene id is required for scene type hotspot.</p>');
485
+ die();
486
+ }
487
+ if (!empty($hotspot_url_validate) || !empty($hotspot_content_validate)) {
488
+ wp_send_json_error('<p><span>Warning:</span> Don\'t add Url or On click content on scene type hotspot.</p>');
489
+ die();
490
+ }
491
+ }
492
  }
493
  }
494
  }
505
  wp_send_json_error('<p><span>Warning:</span> Default scene is required. Set a scene as deafualt to load it by deafult.</p>');
506
  die();
507
  }
508
+
509
+ $panolength = count($panodata["scene-list"]);
510
+ for ($i=0; $i < $panolength; $i++) {
511
+ if (empty($panodata["scene-list"][$i]['scene-id'])) {
512
+ unset($panodata["scene-list"][$i]);
513
+ }
514
+ else {
515
+ $panohotspotlength = count($panodata["scene-list"][$i]['hotspot-list']);
516
+ for ($j=0; $j < $panohotspotlength; $j++) {
517
+ if (empty($panodata["scene-list"][$i]['hotspot-list'][$j]['hotspot-title'])) {
518
+ unset($panodata["scene-list"][$i]['hotspot-list'][$j]);
519
+ }
520
+ }
521
+ }
522
+ }
523
+ // echo "<pre>";
524
+ // var_dump($panodata);
525
+ // die();
526
  $pano_array = array();
527
+ $pano_array = array(__( "panoid" )=>$panoid,__( "autoLoad" )=>$autoload,__( "showControls" )=>$control,__( "autoRotate" )=>$autorotation,__( "autoRotateInactivityDelay" )=>$autorotationinactivedelay,__( "autoRotateStopDelay" )=>$autorotationstopdelay,__( "preview" )=>$preview,__( "defaultscene" )=>$default_scene,__( "scenefadeduration" )=>$scene_fade_duration,__( "panodata" )=>$panodata);
528
+
529
+
530
+ if (empty($autorotation)) {
531
+ unset($pano_array['autoRotate']);
532
+ unset($pano_array['autoRotateInactivityDelay']);
533
+ unset($pano_array['autoRotateStopDelay']);
534
+ }
535
+ if (empty($autorotationinactivedelay)) {
536
+ unset($pano_array['autoRotateInactivityDelay']);
537
+ }
538
+ if (empty($autorotationstopdelay)) {
539
+ unset($pano_array['autoRotateStopDelay']);
540
+ }
541
+
542
  update_post_meta( $postid, 'panodata', $pano_array );
543
  die();
544
  }
admin/css/wpvr-admin.css CHANGED
@@ -94,6 +94,24 @@ div.custom-tooltip:hover span:after {
94
  visibility: visible;
95
  }
96
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
97
  #custom-ifram p {
98
  background-color: #fff;
99
  padding: 10px;
@@ -128,6 +146,85 @@ div.custom-tooltip:hover span:after {
128
  width: 400px;
129
  }
130
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
131
  @media (max-width: 1199px){
132
  .pnlm-container {
133
  width: 100%!important;
@@ -238,6 +335,7 @@ div.custom-tooltip:hover span:after {
238
  font-size: 20px;
239
  margin-right: 5px;
240
  }
 
241
  .rex-pano-tabs .rex-pano-tab-nav li.scene span i {
242
  margin-right: 18px;
243
  }
@@ -301,6 +399,7 @@ div.custom-tooltip:hover span:after {
301
  background: #fff;
302
  width: 78%;
303
  border-left: 1px solid #d5e0fd;
 
304
  }
305
  .rex-pano-tab-content .title {
306
  font-size: 18px;
@@ -326,6 +425,7 @@ div.custom-tooltip:hover span:after {
326
  .rex-pano-tab-content .title i {
327
  margin-right: 10px;
328
  }
 
329
  .rex-pano-tab-content .rex-pano-tab.general {
330
  padding: 50px 40px;
331
  }
@@ -421,6 +521,9 @@ div.custom-tooltip:hover span:after {
421
  color: #555555;
422
  font-weight: 600;
423
  }
 
 
 
424
  .rex-pano-tab .single-settings ul{
425
  margin: 0;
426
  }
@@ -444,6 +547,64 @@ div.custom-tooltip:hover span:after {
444
  border-radius: 3px;
445
  }
446
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
447
 
448
  /*------------sub tab style----------*/
449
  .rex-pano-sub-tabs {
@@ -553,6 +714,9 @@ div.custom-tooltip:hover span:after {
553
  display: block;
554
  margin-bottom: 10px;
555
  }
 
 
 
556
  .single-hotspot .hotspot-setting select,
557
  .single-hotspot .hotspot-setting textarea,
558
  .single-hotspot .hotspot-setting input,
@@ -567,11 +731,15 @@ div.custom-tooltip:hover span:after {
567
  color: #454545;
568
  border-radius: 3px;
569
  }
 
570
  .single-scene .scene-setting .form-group img {
571
  max-width: 400px;
572
  width: 100%;
 
573
  margin-bottom: 15px;
574
  }
 
 
575
  .single-scene .scene-setting input.scene-upload {
576
  text-transform: uppercase;
577
  font-weight: 500;
@@ -581,6 +749,8 @@ div.custom-tooltip:hover span:after {
581
  cursor: pointer;
582
  transition: all 0.3s ease;
583
  }
 
 
584
  .single-scene .scene-setting input.scene-upload:hover {
585
  background: #3869f3;
586
  }
@@ -589,9 +759,32 @@ div.custom-tooltip:hover span:after {
589
  outline: inherit;
590
  }
591
 
 
 
 
 
 
 
 
 
 
 
 
 
 
592
 
593
-
594
-
 
 
 
 
 
 
 
 
 
 
595
 
596
  /*--------------------hotspot style-------------------*/
597
  .clearfix::before,
@@ -672,6 +865,7 @@ button.delete-hotspot{
672
  padding: 55px 0px;
673
  border-top: 1px solid #e4eafe;
674
  }
 
675
  .preview-btn-area #panolenspreview {
676
  text-transform: capitalize;
677
  color: #fff;
@@ -689,27 +883,39 @@ button.delete-hotspot{
689
  box-shadow: 0px 7px 25px 0px rgba(56, 84, 163, 0.2);
690
  float: right;
691
  }
 
692
  .preview-btn-area #panolenspreview:hover {
693
  background: #3869f3;
694
  box-shadow: none;
695
  }
696
 
697
- .preview-btn-area #error_occured {
698
- display: block;
699
- min-width: 150px;
700
  min-height: 15px;
701
- float: left;
702
- }
703
- .preview-btn-area #error_occured p {
 
 
 
 
 
 
 
 
 
 
 
 
704
  background: #fffbfb;
705
  padding: 10px 25px;
706
  margin: 0;
707
  border: 1px solid #ffd9de;
708
  color: #f93333;
709
  border-radius: 5px;
710
- font-size: 16px;
711
  }
712
- .preview-btn-area #error_occured p span{
713
  font-weight: 500;
714
  }
715
 
@@ -720,6 +926,7 @@ button.delete-hotspot{
720
 
721
 
722
 
 
723
  /*-----------------responsive style------------------*/
724
  @media (max-width: 1700px){
725
  .rex-pano-tabs .main-nav li {
@@ -777,6 +984,17 @@ button.delete-hotspot{
777
 
778
  }
779
 
 
 
 
 
 
 
 
 
 
 
 
780
  @media (max-width: 1199px){
781
  .rex-pano-tab-content {
782
  border-left: none;
@@ -792,6 +1010,7 @@ button.delete-hotspot{
792
  .rex-pano-tabs .main-nav li span {
793
  padding: 0 10px;
794
  }
 
795
  .rex-pano-tab-content .rex-pano-tab.general {
796
  padding: 20px 20px;
797
  }
@@ -830,7 +1049,12 @@ button.delete-hotspot{
830
  input.delete-hotspot {
831
  right: 5px;
832
  top: 35px;
833
- }
 
 
 
 
 
834
  }
835
 
836
  /**
94
  visibility: visible;
95
  }
96
 
97
+ .post-type-wpvr_item:after {
98
+ content: "";
99
+ background: rgba(0, 0, 0, 0.54);
100
+ top: 0;
101
+ left: 0;
102
+ width: 100%;
103
+ height: 100%;
104
+ position: fixed;
105
+ opacity: 0;
106
+ visibility: hidden;
107
+ transition: all 0.4s ease;
108
+ }
109
+
110
+ .post-type-wpvr_item.error-overlay:after {
111
+ opacity: 1;
112
+ visibility: visible;
113
+ }
114
+
115
  #custom-ifram p {
116
  background-color: #fff;
117
  padding: 10px;
146
  width: 400px;
147
  }
148
 
149
+ .rex-add-coordinates ul li {
150
+ display: inline-block;
151
+ text-align: center !important;
152
+ cursor: pointer;
153
+ vertical-align: middle;
154
+ margin-bottom: 0;
155
+ }
156
+ .rex-add-coordinates ul li #panodata {
157
+ margin-top: 0!important;
158
+ font-size: 13px;
159
+ }
160
+ .rex-add-coordinates ul li.add-pitch {
161
+ margin-bottom: 0;
162
+ width: 20px;
163
+ height: 20px;
164
+ background: #222;
165
+ color: #fff;
166
+ border-radius: 100%;
167
+ box-sizing: border-box;
168
+ margin-left: 6px;
169
+ position: relative;
170
+ }
171
+
172
+ .rex-hide-coordinates {
173
+ display: none !important;
174
+ }
175
+
176
+ .toppitch {
177
+ font-size: 11px;
178
+ vertical-align: middle;
179
+ }
180
+
181
+ .rex-add-coordinates ul li.add-pitch .rex-tooltiptext {
182
+ visibility: hidden;
183
+ opacity: 0;
184
+ width: auto;
185
+ background-color: #222;
186
+ color: #fff;
187
+ text-align: center;
188
+ border-radius: 5px;
189
+ position: absolute;
190
+ z-index: 1;
191
+ top: -100%;
192
+ left: 50%;
193
+ padding: 6px 12px;
194
+ white-space: nowrap;
195
+ font-size: 11px;
196
+ margin-top: -16px;
197
+ letter-spacing: 0.5px;
198
+ -webkit-transform: translateX(-50%);
199
+ -moz-transform: translateX(-50%);
200
+ -ms-transform: translateX(-50%);
201
+ -o-transform: translateX(-50%);
202
+ transform: translateX(-50%);
203
+
204
+ -webkit-transition: all 0.5s ease;
205
+ -moz-transition: all 0.5s ease;
206
+ -ms-transition: all 0.5s ease;
207
+ -o-transition: all 0.5s ease;
208
+ transition: all 0.5s ease;
209
+ }
210
+
211
+ .rex-add-coordinates ul li.add-pitch .rex-tooltiptext::after {
212
+ content: "";
213
+ position: absolute;
214
+ top: 100%;
215
+ left: 50%;
216
+ margin-left: -5px;
217
+ border-width: 5px;
218
+ border-style: solid;
219
+ border-color: #222 transparent transparent transparent;
220
+ }
221
+
222
+ .rex-add-coordinates ul li.add-pitch:hover .rex-tooltiptext {
223
+ visibility: visible;
224
+ margin-top: -18px;
225
+ opacity: 1;
226
+ }
227
+
228
  @media (max-width: 1199px){
229
  .pnlm-container {
230
  width: 100%!important;
335
  font-size: 20px;
336
  margin-right: 5px;
337
  }
338
+ .rex-pano-tabs .rex-pano-tab-nav li.video span i,
339
  .rex-pano-tabs .rex-pano-tab-nav li.scene span i {
340
  margin-right: 18px;
341
  }
399
  background: #fff;
400
  width: 78%;
401
  border-left: 1px solid #d5e0fd;
402
+ position: relative;
403
  }
404
  .rex-pano-tab-content .title {
405
  font-size: 18px;
425
  .rex-pano-tab-content .title i {
426
  margin-right: 10px;
427
  }
428
+ .rex-pano-tab-content .rex-pano-tab.video,
429
  .rex-pano-tab-content .rex-pano-tab.general {
430
  padding: 50px 40px;
431
  }
521
  color: #555555;
522
  font-weight: 600;
523
  }
524
+ .rex-pano-tab.general .single-settings span {
525
+ min-width: 240px;
526
+ }
527
  .rex-pano-tab .single-settings ul{
528
  margin: 0;
529
  }
547
  border-radius: 3px;
548
  }
549
 
550
+ /*------------input field tooltip-----------*/
551
+ .rex-pano-tab.general .single-settings .field-tooltip {
552
+ position: relative;
553
+ padding-left: 10px;
554
+ }
555
+ .rex-pano-tab.general .single-settings .field-tooltip i {
556
+ font-size: 20px;
557
+ cursor: help;
558
+ }
559
+ .rex-pano-tab.general .single-settings .field-tooltip span {
560
+ position: absolute;
561
+ top: 50%;
562
+ font-weight: 300;
563
+ background: #444;
564
+ color: #fff;
565
+ display: inline-block;
566
+ text-align: center;
567
+ min-width: 240px;
568
+ max-width: 280px;
569
+ padding: 7px 8px;
570
+ font-size: 14px;
571
+ left: 40px;
572
+ line-height: 20px;
573
+ border-radius: 3px;
574
+ opacity: 0;
575
+ visibility: hidden;
576
+ -webkit-transform: translateY(-50%);
577
+ -moz-transform: translateY(-50%);
578
+ -ms-transform: translateY(-50%);
579
+ -o-transform: translateY(-50%);
580
+ transform: translateY(-50%);
581
+
582
+ -webkit-transition: all 0.5s ease;
583
+ -moz-transition: all 0.5s ease;
584
+ -ms-transition: all 0.5s ease;
585
+ -o-transition: all 0.5s ease;
586
+ transition: all 0.5s ease;
587
+ }
588
+ .rex-pano-tab.general .single-settings .field-tooltip span:before {
589
+ content: "";
590
+ position: absolute;
591
+ left: -9px;
592
+ top: 50%;
593
+ width: 0;
594
+ height: 0;
595
+ border-top: 6px solid transparent;
596
+ border-right: 9px solid #444;
597
+ border-bottom: 6px solid transparent;
598
+ -webkit-transform: translateY(-50%);
599
+ -moz-transform: translateY(-50%);
600
+ -ms-transform: translateY(-50%);
601
+ -o-transform: translateY(-50%);
602
+ transform: translateY(-50%);
603
+ }
604
+ .rex-pano-tab.general .single-settings .field-tooltip:hover span {
605
+ opacity: 1;
606
+ visibility: visible;
607
+ }
608
 
609
  /*------------sub tab style----------*/
610
  .rex-pano-sub-tabs {
714
  display: block;
715
  margin-bottom: 10px;
716
  }
717
+
718
+ .rex-pano-tab .preview-setting input.preview-upload,
719
+ .rex-pano-tab .video-setting input.video-upload,
720
  .single-hotspot .hotspot-setting select,
721
  .single-hotspot .hotspot-setting textarea,
722
  .single-hotspot .hotspot-setting input,
731
  color: #454545;
732
  border-radius: 3px;
733
  }
734
+ .single-settings.preview-setting .form-group img,
735
  .single-scene .scene-setting .form-group img {
736
  max-width: 400px;
737
  width: 100%;
738
+ display: block;
739
  margin-bottom: 15px;
740
  }
741
+ .rex-pano-tab .preview-setting input.preview-upload,
742
+ .rex-pano-tab .video-setting input.video-upload,
743
  .single-scene .scene-setting input.scene-upload {
744
  text-transform: uppercase;
745
  font-weight: 500;
749
  cursor: pointer;
750
  transition: all 0.3s ease;
751
  }
752
+ .rex-pano-tab .preview-setting input.preview-upload:hover,
753
+ .rex-pano-tab .video-setting .single-settings input.video-upload:hover,
754
  .single-scene .scene-setting input.scene-upload:hover {
755
  background: #3869f3;
756
  }
759
  outline: inherit;
760
  }
761
 
762
+ .rex-pano-tab .video-setting .single-settings {
763
+ align-items: flex-start;
764
+ }
765
+ .rex-pano-tab .video-setting input.video-upload {
766
+ max-width: 160px;
767
+ margin-top: 20px;
768
+ }
769
+ .rex-pano-tab .video-setting:before,
770
+ .rex-pano-tab .video-setting:after {
771
+ content: "";
772
+ display: table;
773
+ clear: both;
774
+ }
775
 
776
+ .rex-pano-tab .single-settings.preview-setting {
777
+ display: block;
778
+ }
779
+ .single-settings.preview-setting .form-group input {
780
+ margin-bottom: 15px;
781
+ }
782
+ .rex-pano-tab .single-settings.preview-setting span {
783
+ margin-bottom: 10px;
784
+ }
785
+ .rex-pano-tab .single-settings.scene-fade-duration input {
786
+ width: 230px;
787
+ }
788
 
789
  /*--------------------hotspot style-------------------*/
790
  .clearfix::before,
865
  padding: 55px 0px;
866
  border-top: 1px solid #e4eafe;
867
  }
868
+ .video-setting button#videopreview,
869
  .preview-btn-area #panolenspreview {
870
  text-transform: capitalize;
871
  color: #fff;
883
  box-shadow: 0px 7px 25px 0px rgba(56, 84, 163, 0.2);
884
  float: right;
885
  }
886
+ .video-setting button#videopreview:hover,
887
  .preview-btn-area #panolenspreview:hover {
888
  background: #3869f3;
889
  box-shadow: none;
890
  }
891
 
892
+ #error_occured {
893
+ display: none;
 
894
  min-height: 15px;
895
+ position: absolute;
896
+ top: 50%;
897
+ right: 50%;
898
+ background: #eee;
899
+ padding: 50px 50px;
900
+ min-width: 450px;
901
+ border-radius: 5px;
902
+ -webkit-transform: translate(50%, -50%);
903
+ -moz-transform: translate(50%, -50%);
904
+ -ms-transform: translate(50%, -50%);
905
+ -o-transform: translate(50%, -50%);
906
+ transform: translate(50%, -50%);
907
+ z-index: 2;
908
+ }
909
+ #error_occured p {
910
  background: #fffbfb;
911
  padding: 10px 25px;
912
  margin: 0;
913
  border: 1px solid #ffd9de;
914
  color: #f93333;
915
  border-radius: 5px;
916
+ font-size: 14px;
917
  }
918
+ #error_occured p span{
919
  font-weight: 500;
920
  }
921
 
926
 
927
 
928
 
929
+
930
  /*-----------------responsive style------------------*/
931
  @media (max-width: 1700px){
932
  .rex-pano-tabs .main-nav li {
984
 
985
  }
986
 
987
+ @media (max-width: 1300px){
988
+ .preview-btn-area #error_occured {
989
+ float: none;
990
+ margin-bottom: 20px;
991
+ }
992
+ .preview-btn-area #panolenspreview {
993
+ float: none;
994
+ }
995
+
996
+ }
997
+
998
  @media (max-width: 1199px){
999
  .rex-pano-tab-content {
1000
  border-left: none;
1010
  .rex-pano-tabs .main-nav li span {
1011
  padding: 0 10px;
1012
  }
1013
+ .rex-pano-tab-content .rex-pano-tab.video,
1014
  .rex-pano-tab-content .rex-pano-tab.general {
1015
  padding: 20px 20px;
1016
  }
1049
  input.delete-hotspot {
1050
  right: 5px;
1051
  top: 35px;
1052
+ }
1053
+
1054
+ .rex-pano-tab .single-settings.scene-fade-duration input {
1055
+ width: 160px;
1056
+ }
1057
+
1058
  }
1059
 
1060
  /**
admin/js/video.js ADDED
@@ -0,0 +1,48094 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * @license
3
+ * Video.js 7.1.0 <http://videojs.com/>
4
+ * Copyright Brightcove, Inc. <https://www.brightcove.com/>
5
+ * Available under Apache License Version 2.0
6
+ * <https://github.com/videojs/video.js/blob/master/LICENSE>
7
+ *
8
+ * Includes vtt.js <https://github.com/mozilla/vtt.js>
9
+ * Available under Apache License Version 2.0
10
+ * <https://github.com/mozilla/vtt.js/blob/master/LICENSE>
11
+ */
12
+
13
+ (function (global, factory) {
14
+ typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
15
+ typeof define === 'function' && define.amd ? define(factory) :
16
+ (global.videojs = factory());
17
+ }(this, (function () {
18
+ var version = "7.1.0";
19
+
20
+ var commonjsGlobal = typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
21
+
22
+ function createCommonjsModule(fn, module) {
23
+ return module = { exports: {} }, fn(module, module.exports), module.exports;
24
+ }
25
+
26
+ var win;
27
+
28
+ if (typeof window !== "undefined") {
29
+ win = window;
30
+ } else if (typeof commonjsGlobal !== "undefined") {
31
+ win = commonjsGlobal;
32
+ } else if (typeof self !== "undefined") {
33
+ win = self;
34
+ } else {
35
+ win = {};
36
+ }
37
+
38
+ var window_1 = win;
39
+
40
+ var empty = {};
41
+
42
+ var empty$1 = /*#__PURE__*/Object.freeze({
43
+ default: empty
44
+ });
45
+
46
+ var minDoc = ( empty$1 && empty ) || empty$1;
47
+
48
+ var topLevel = typeof commonjsGlobal !== 'undefined' ? commonjsGlobal : typeof window !== 'undefined' ? window : {};
49
+
50
+ var doccy;
51
+
52
+ if (typeof document !== 'undefined') {
53
+ doccy = document;
54
+ } else {
55
+ doccy = topLevel['__GLOBAL_DOCUMENT_CACHE@4'];
56
+
57
+ if (!doccy) {
58
+ doccy = topLevel['__GLOBAL_DOCUMENT_CACHE@4'] = minDoc;
59
+ }
60
+ }
61
+
62
+ var document_1 = doccy;
63
+
64
+ /**
65
+ * @file log.js
66
+ * @module log
67
+ */
68
+
69
+ var log = void 0;
70
+
71
+ // This is the private tracking variable for logging level.
72
+ var level = 'info';
73
+
74
+ // This is the private tracking variable for the logging history.
75
+ var history = [];
76
+
77
+ /**
78
+ * Log messages to the console and history based on the type of message
79
+ *
80
+ * @private
81
+ * @param {string} type
82
+ * The name of the console method to use.
83
+ *
84
+ * @param {Array} args
85
+ * The arguments to be passed to the matching console method.
86
+ */
87
+ var logByType = function logByType(type, args) {
88
+ var lvl = log.levels[level];
89
+ var lvlRegExp = new RegExp('^(' + lvl + ')$');
90
+
91
+ if (type !== 'log') {
92
+
93
+ // Add the type to the front of the message when it's not "log".
94
+ args.unshift(type.toUpperCase() + ':');
95
+ }
96
+
97
+ // Add a clone of the args at this point to history.
98
+ if (history) {
99
+ history.push([].concat(args));
100
+ }
101
+
102
+ // Add console prefix after adding to history.
103
+ args.unshift('VIDEOJS:');
104
+
105
+ // If there's no console then don't try to output messages, but they will
106
+ // still be stored in history.
107
+ if (!window_1.console) {
108
+ return;
109
+ }
110
+
111
+ // Was setting these once outside of this function, but containing them
112
+ // in the function makes it easier to test cases where console doesn't exist
113
+ // when the module is executed.
114
+ var fn = window_1.console[type];
115
+
116
+ if (!fn && type === 'debug') {
117
+ // Certain browsers don't have support for console.debug. For those, we
118
+ // should default to the closest comparable log.
119
+ fn = window_1.console.info || window_1.console.log;
120
+ }
121
+
122
+ // Bail out if there's no console or if this type is not allowed by the
123
+ // current logging level.
124
+ if (!fn || !lvl || !lvlRegExp.test(type)) {
125
+ return;
126
+ }
127
+
128
+ fn[Array.isArray(args) ? 'apply' : 'call'](window_1.console, args);
129
+ };
130
+
131
+ /**
132
+ * Logs plain debug messages. Similar to `console.log`.
133
+ *
134
+ * @class
135
+ * @param {Mixed[]} args
136
+ * One or more messages or objects that should be logged.
137
+ */
138
+ log = function log() {
139
+ for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
140
+ args[_key] = arguments[_key];
141
+ }
142
+
143
+ logByType('log', args);
144
+ };
145
+
146
+ /**
147
+ * Enumeration of available logging levels, where the keys are the level names
148
+ * and the values are `|`-separated strings containing logging methods allowed
149
+ * in that logging level. These strings are used to create a regular expression
150
+ * matching the function name being called.
151
+ *
152
+ * Levels provided by video.js are:
153
+ *
154
+ * - `off`: Matches no calls. Any value that can be cast to `false` will have
155
+ * this effect. The most restrictive.
156
+ * - `all`: Matches only Video.js-provided functions (`debug`, `log`,
157
+ * `log.warn`, and `log.error`).
158
+ * - `debug`: Matches `log.debug`, `log`, `log.warn`, and `log.error` calls.
159
+ * - `info` (default): Matches `log`, `log.warn`, and `log.error` calls.
160
+ * - `warn`: Matches `log.warn` and `log.error` calls.
161
+ * - `error`: Matches only `log.error` calls.
162
+ *
163
+ * @type {Object}
164
+ */
165
+ log.levels = {
166
+ all: 'debug|log|warn|error',
167
+ off: '',
168
+ debug: 'debug|log|warn|error',
169
+ info: 'log|warn|error',
170
+ warn: 'warn|error',
171
+ error: 'error',
172
+ DEFAULT: level
173
+ };
174
+
175
+ /**
176
+ * Get or set the current logging level. If a string matching a key from
177
+ * {@link log.levels} is provided, acts as a setter. Regardless of argument,
178
+ * returns the current logging level.
179
+ *
180
+ * @param {string} [lvl]
181
+ * Pass to set a new logging level.
182
+ *
183
+ * @return {string}
184
+ * The current logging level.
185
+ */
186
+ log.level = function (lvl) {
187
+ if (typeof lvl === 'string') {
188
+ if (!log.levels.hasOwnProperty(lvl)) {
189
+ throw new Error('"' + lvl + '" in not a valid log level');
190
+ }
191
+ level = lvl;
192
+ }
193
+ return level;
194
+ };
195
+
196
+ /**
197
+ * Returns an array containing everything that has been logged to the history.
198
+ *
199
+ * This array is a shallow clone of the internal history record. However, its
200
+ * contents are _not_ cloned; so, mutating objects inside this array will
201
+ * mutate them in history.
202
+ *
203
+ * @return {Array}
204
+ */
205
+ log.history = function () {
206
+ return history ? [].concat(history) : [];
207
+ };
208
+
209
+ /**
210
+ * Clears the internal history tracking, but does not prevent further history
211
+ * tracking.
212
+ */
213
+ log.history.clear = function () {
214
+ if (history) {
215
+ history.length = 0;
216
+ }
217
+ };
218
+
219
+ /**
220
+ * Disable history tracking if it is currently enabled.
221
+ */
222
+ log.history.disable = function () {
223
+ if (history !== null) {
224
+ history.length = 0;
225
+ history = null;
226
+ }
227
+ };
228
+
229
+ /**
230
+ * Enable history tracking if it is currently disabled.
231
+ */
232
+ log.history.enable = function () {
233
+ if (history === null) {
234
+ history = [];
235
+ }
236
+ };
237
+
238
+ /**
239
+ * Logs error messages. Similar to `console.error`.
240
+ *
241
+ * @param {Mixed[]} args
242
+ * One or more messages or objects that should be logged as an error
243
+ */
244
+ log.error = function () {
245
+ for (var _len2 = arguments.length, args = Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
246
+ args[_key2] = arguments[_key2];
247
+ }
248
+
249
+ return logByType('error', args);
250
+ };
251
+
252
+ /**
253
+ * Logs warning messages. Similar to `console.warn`.
254
+ *
255
+ * @param {Mixed[]} args
256
+ * One or more messages or objects that should be logged as a warning.
257
+ */
258
+ log.warn = function () {
259
+ for (var _len3 = arguments.length, args = Array(_len3), _key3 = 0; _key3 < _len3; _key3++) {
260
+ args[_key3] = arguments[_key3];
261
+ }
262
+
263
+ return logByType('warn', args);
264
+ };
265
+
266
+ /**
267
+ * Logs debug messages. Similar to `console.debug`, but may also act as a comparable
268
+ * log if `console.debug` is not available
269
+ *
270
+ * @param {Mixed[]} args
271
+ * One or more messages or objects that should be logged as debug.
272
+ */
273
+ log.debug = function () {
274
+ for (var _len4 = arguments.length, args = Array(_len4), _key4 = 0; _key4 < _len4; _key4++) {
275
+ args[_key4] = arguments[_key4];
276
+ }
277
+
278
+ return logByType('debug', args);
279
+ };
280
+
281
+ var log$1 = log;
282
+
283
+ function clean(s) {
284
+ return s.replace(/\n\r?\s*/g, '');
285
+ }
286
+
287
+ var tsml = function tsml(sa) {
288
+ var s = '',
289
+ i = 0;
290
+
291
+ for (; i < arguments.length; i++) {
292
+ s += clean(sa[i]) + (arguments[i + 1] || '');
293
+ }return s;
294
+ };
295
+
296
+ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) {
297
+ return typeof obj;
298
+ } : function (obj) {
299
+ return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj;
300
+ };
301
+
302
+ var classCallCheck = function (instance, Constructor) {
303
+ if (!(instance instanceof Constructor)) {
304
+ throw new TypeError("Cannot call a class as a function");
305
+ }
306
+ };
307
+
308
+ var inherits = function (subClass, superClass) {
309
+ if (typeof superClass !== "function" && superClass !== null) {
310
+ throw new TypeError("Super expression must either be null or a function, not " + typeof superClass);
311
+ }
312
+
313
+ subClass.prototype = Object.create(superClass && superClass.prototype, {
314
+ constructor: {
315
+ value: subClass,
316
+ enumerable: false,
317
+ writable: true,
318
+ configurable: true
319
+ }
320
+ });
321
+ if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass;
322
+ };
323
+
324
+ var possibleConstructorReturn = function (self, call) {
325
+ if (!self) {
326
+ throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
327
+ }
328
+
329
+ return call && (typeof call === "object" || typeof call === "function") ? call : self;
330
+ };
331
+
332
+ var taggedTemplateLiteralLoose = function (strings, raw) {
333
+ strings.raw = raw;
334
+ return strings;
335
+ };
336
+
337
+ /**
338
+ * @file obj.js
339
+ * @module obj
340
+ */
341
+
342
+ /**
343
+ * @callback obj:EachCallback
344
+ *
345
+ * @param {Mixed} value
346
+ * The current key for the object that is being iterated over.
347
+ *
348
+ * @param {string} key
349
+ * The current key-value for object that is being iterated over
350
+ */
351
+
352
+ /**
353
+ * @callback obj:ReduceCallback
354
+ *
355
+ * @param {Mixed} accum
356
+ * The value that is accumulating over the reduce loop.
357
+ *
358
+ * @param {Mixed} value
359
+ * The current key for the object that is being iterated over.
360
+ *
361
+ * @param {string} key
362
+ * The current key-value for object that is being iterated over
363
+ *
364
+ * @return {Mixed}
365
+ * The new accumulated value.
366
+ */
367
+ var toString = Object.prototype.toString;
368
+
369
+ /**
370
+ * Get the keys of an Object
371
+ *
372
+ * @param {Object}
373
+ * The Object to get the keys from
374
+ *
375
+ * @return {string[]}
376
+ * An array of the keys from the object. Returns an empty array if the
377
+ * object passed in was invalid or had no keys.
378
+ *
379
+ * @private
380
+ */
381
+ var keys = function keys(object) {
382
+ return isObject(object) ? Object.keys(object) : [];
383
+ };
384
+
385
+ /**
386
+ * Array-like iteration for objects.
387
+ *
388
+ * @param {Object} object
389
+ * The object to iterate over
390
+ *
391
+ * @param {obj:EachCallback} fn
392
+ * The callback function which is called for each key in the object.
393
+ */
394
+ function each(object, fn) {
395
+ keys(object).forEach(function (key) {
396
+ return fn(object[key], key);
397
+ });
398
+ }
399
+
400
+ /**
401
+ * Array-like reduce for objects.
402
+ *
403
+ * @param {Object} object
404
+ * The Object that you want to reduce.
405
+ *
406
+ * @param {Function} fn
407
+ * A callback function which is called for each key in the object. It
408
+ * receives the accumulated value and the per-iteration value and key
409
+ * as arguments.
410
+ *
411
+ * @param {Mixed} [initial = 0]
412
+ * Starting value
413
+ *
414
+ * @return {Mixed}
415
+ * The final accumulated value.
416
+ */
417
+ function reduce(object, fn) {
418
+ var initial = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 0;
419
+
420
+ return keys(object).reduce(function (accum, key) {
421
+ return fn(accum, object[key], key);
422
+ }, initial);
423
+ }
424
+
425
+ /**
426
+ * Object.assign-style object shallow merge/extend.
427
+ *
428
+ * @param {Object} target
429
+ * @param {Object} ...sources
430
+ * @return {Object}
431
+ */
432
+ function assign(target) {
433
+ for (var _len = arguments.length, sources = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
434
+ sources[_key - 1] = arguments[_key];
435
+ }
436
+
437
+ if (Object.assign) {
438
+ return Object.assign.apply(Object, [target].concat(sources));
439
+ }
440
+
441
+ sources.forEach(function (source) {
442
+ if (!source) {
443
+ return;
444
+ }
445
+
446
+ each(source, function (value, key) {
447
+ target[key] = value;
448
+ });
449
+ });
450
+
451
+ return target;
452
+ }
453
+
454
+ /**
455
+ * Returns whether a value is an object of any kind - including DOM nodes,
456
+ * arrays, regular expressions, etc. Not functions, though.
457
+ *
458
+ * This avoids the gotcha where using `typeof` on a `null` value
459
+ * results in `'object'`.
460
+ *
461
+ * @param {Object} value
462
+ * @return {Boolean}
463
+ */
464
+ function isObject(value) {
465
+ return !!value && (typeof value === 'undefined' ? 'undefined' : _typeof(value)) === 'object';
466
+ }
467
+
468
+ /**
469
+ * Returns whether an object appears to be a "plain" object - that is, a
470
+ * direct instance of `Object`.
471
+ *
472
+ * @param {Object} value
473
+ * @return {Boolean}
474
+ */
475
+ function isPlain(value) {
476
+ return isObject(value) && toString.call(value) === '[object Object]' && value.constructor === Object;
477
+ }
478
+
479
+ /**
480
+ * @file computed-style.js
481
+ * @module computed-style
482
+ */
483
+
484
+ /**
485
+ * A safe getComputedStyle.
486
+ *
487
+ * This is needed because in Firefox, if the player is loaded in an iframe with
488
+ * `display:none`, then `getComputedStyle` returns `null`, so, we do a null-check to
489
+ * make sure that the player doesn't break in these cases.
490
+ *
491
+ * @param {Element} el
492
+ * The element you want the computed style of
493
+ *
494
+ * @param {string} prop
495
+ * The property name you want
496
+ *
497
+ * @see https://bugzilla.mozilla.org/show_bug.cgi?id=548397
498
+ *
499
+ * @static
500
+ * @const
501
+ */
502
+ function computedStyle(el, prop) {
503
+ if (!el || !prop) {
504
+ return '';
505
+ }
506
+
507
+ if (typeof window_1.getComputedStyle === 'function') {
508
+ var cs = window_1.getComputedStyle(el);
509
+
510
+ return cs ? cs[prop] : '';
511
+ }
512
+
513
+ return '';
514
+ }
515
+
516
+ var _templateObject = taggedTemplateLiteralLoose(['Setting attributes in the second argument of createEl()\n has been deprecated. Use the third argument instead.\n createEl(type, properties, attributes). Attempting to set ', ' to ', '.'], ['Setting attributes in the second argument of createEl()\n has been deprecated. Use the third argument instead.\n createEl(type, properties, attributes). Attempting to set ', ' to ', '.']);
517
+
518
+ /**
519
+ * Detect if a value is a string with any non-whitespace characters.
520
+ *
521
+ * @param {string} str
522
+ * The string to check
523
+ *
524
+ * @return {boolean}
525
+ * - True if the string is non-blank
526
+ * - False otherwise
527
+ *
528
+ */
529
+ function isNonBlankString(str) {
530
+ return typeof str === 'string' && /\S/.test(str);
531
+ }
532
+
533
+ /**
534
+ * Throws an error if the passed string has whitespace. This is used by
535
+ * class methods to be relatively consistent with the classList API.
536
+ *
537
+ * @param {string} str
538
+ * The string to check for whitespace.
539
+ *
540
+ * @throws {Error}
541
+ * Throws an error if there is whitespace in the string.
542
+ *
543
+ */
544
+ function throwIfWhitespace(str) {
545
+ if (/\s/.test(str)) {
546
+ throw new Error('class has illegal whitespace characters');
547
+ }
548
+ }
549
+
550
+ /**
551
+ * Produce a regular expression for matching a className within an elements className.
552
+ *
553
+ * @param {string} className
554
+ * The className to generate the RegExp for.
555
+ *
556
+ * @return {RegExp}
557
+ * The RegExp that will check for a specific `className` in an elements
558
+ * className.
559
+ */
560
+ function classRegExp(className) {
561
+ return new RegExp('(^|\\s)' + className + '($|\\s)');
562
+ }
563
+
564
+ /**
565
+ * Whether the current DOM interface appears to be real.
566
+ *
567
+ * @return {Boolean}
568
+ */
569
+ function isReal() {
570
+ // Both document and window will never be undefined thanks to `global`.
571
+ return document_1 === window_1.document;
572
+ }
573
+
574
+ /**
575
+ * Determines, via duck typing, whether or not a value is a DOM element.
576
+ *
577
+ * @param {Mixed} value
578
+ * The thing to check
579
+ *
580
+ * @return {boolean}
581
+ * - True if it is a DOM element
582
+ * - False otherwise
583
+ */
584
+ function isEl(value) {
585
+ return isObject(value) && value.nodeType === 1;
586
+ }
587
+
588
+ /**
589
+ * Determines if the current DOM is embedded in an iframe.
590
+ *
591
+ * @return {boolean}
592
+ *
593
+ */
594
+ function isInFrame() {
595
+
596
+ // We need a try/catch here because Safari will throw errors when attempting
597
+ // to get either `parent` or `self`
598
+ try {
599
+ return window_1.parent !== window_1.self;
600
+ } catch (x) {
601
+ return true;
602
+ }
603
+ }
604
+
605
+ /**
606
+ * Creates functions to query the DOM using a given method.
607
+ *
608
+ * @param {string} method
609
+ * The method to create the query with.
610
+ *
611
+ * @return {Function}
612
+ * The query method
613
+ */
614
+ function createQuerier(method) {
615
+ return function (selector, context) {
616
+ if (!isNonBlankString(selector)) {
617
+ return document_1[method](null);
618
+ }
619
+ if (isNonBlankString(context)) {
620
+ context = document_1.querySelector(context);
621
+ }
622
+
623
+ var ctx = isEl(context) ? context : document_1;
624
+
625
+ return ctx[method] && ctx[method](selector);
626
+ };
627
+ }
628
+
629
+ /**
630
+ * Creates an element and applies properties.
631
+ *
632
+ * @param {string} [tagName='div']
633
+ * Name of tag to be created.
634
+ *
635
+ * @param {Object} [properties={}]
636
+ * Element properties to be applied.
637
+ *
638
+ * @param {Object} [attributes={}]
639
+ * Element attributes to be applied.
640
+ *
641
+ * @param {String|Element|TextNode|Array|Function} [content]
642
+ * Contents for the element (see: {@link dom:normalizeContent})
643
+ *
644
+ * @return {Element}
645
+ * The element that was created.
646
+ */
647
+ function createEl() {
648
+ var tagName = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'div';
649
+ var properties = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
650
+ var attributes = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
651
+ var content = arguments[3];
652
+
653
+ var el = document_1.createElement(tagName);
654
+
655
+ Object.getOwnPropertyNames(properties).forEach(function (propName) {
656
+ var val = properties[propName];
657
+
658
+ // See #2176
659
+ // We originally were accepting both properties and attributes in the
660
+ // same object, but that doesn't work so well.
661
+ if (propName.indexOf('aria-') !== -1 || propName === 'role' || propName === 'type') {
662
+ log$1.warn(tsml(_templateObject, propName, val));
663
+ el.setAttribute(propName, val);
664
+
665
+ // Handle textContent since it's not supported everywhere and we have a
666
+ // method for it.
667
+ } else if (propName === 'textContent') {
668
+ textContent(el, val);
669
+ } else {
670
+ el[propName] = val;
671
+ }
672
+ });
673
+
674
+ Object.getOwnPropertyNames(attributes).forEach(function (attrName) {
675
+ el.setAttribute(attrName, attributes[attrName]);
676
+ });
677
+
678
+ if (content) {
679
+ appendContent(el, content);
680
+ }
681
+
682
+ return el;
683
+ }
684
+
685
+ /**
686
+ * Injects text into an element, replacing any existing contents entirely.
687
+ *
688
+ * @param {Element} el
689
+ * The element to add text content into
690
+ *
691
+ * @param {string} text
692
+ * The text content to add.
693
+ *
694
+ * @return {Element}
695
+ * The element with added text content.
696
+ */
697
+ function textContent(el, text) {
698
+ if (typeof el.textContent === 'undefined') {
699
+ el.innerText = text;
700
+ } else {
701
+ el.textContent = text;
702
+ }
703
+ return el;
704
+ }
705
+
706
+ /**
707
+ * Insert an element as the first child node of another
708
+ *
709
+ * @param {Element} child
710
+ * Element to insert
711
+ *
712
+ * @param {Element} parent
713
+ * Element to insert child into
714
+ */
715
+ function prependTo(child, parent) {
716
+ if (parent.firstChild) {
717
+ parent.insertBefore(child, parent.firstChild);
718
+ } else {
719
+ parent.appendChild(child);
720
+ }
721
+ }
722
+
723
+ /**
724
+ * Check if an element has a CSS class
725
+ *
726
+ * @param {Element} element
727
+ * Element to check
728
+ *
729
+ * @param {string} classToCheck
730
+ * Class name to check for
731
+ *
732
+ * @return {boolean}
733
+ * - True if the element had the class
734
+ * - False otherwise.
735
+ *
736
+ * @throws {Error}
737
+ * Throws an error if `classToCheck` has white space.
738
+ */
739
+ function hasClass(element, classToCheck) {
740
+ throwIfWhitespace(classToCheck);
741
+ if (element.classList) {
742
+ return element.classList.contains(classToCheck);
743
+ }
744
+ return classRegExp(classToCheck).test(element.className);
745
+ }
746
+
747
+ /**
748
+ * Add a CSS class name to an element
749
+ *
750
+ * @param {Element} element
751
+ * Element to add class name to.
752
+ *
753
+ * @param {string} classToAdd
754
+ * Class name to add.
755
+ *
756
+ * @return {Element}
757
+ * The dom element with the added class name.
758
+ */
759
+ function addClass(element, classToAdd) {
760
+ if (element.classList) {
761
+ element.classList.add(classToAdd);
762
+
763
+ // Don't need to `throwIfWhitespace` here because `hasElClass` will do it
764
+ // in the case of classList not being supported.
765
+ } else if (!hasClass(element, classToAdd)) {
766
+ element.className = (element.className + ' ' + classToAdd).trim();
767
+ }
768
+
769
+ return element;
770
+ }
771
+
772
+ /**
773
+ * Remove a CSS class name from an element
774
+ *
775
+ * @param {Element} element
776
+ * Element to remove a class name from.
777
+ *
778
+ * @param {string} classToRemove
779
+ * Class name to remove
780
+ *
781
+ * @return {Element}
782
+ * The dom element with class name removed.
783
+ */
784
+ function removeClass(element, classToRemove) {
785
+ if (element.classList) {
786
+ element.classList.remove(classToRemove);
787
+ } else {
788
+ throwIfWhitespace(classToRemove);
789
+ element.className = element.className.split(/\s+/).filter(function (c) {
790
+ return c !== classToRemove;
791
+ }).join(' ');
792
+ }
793
+
794
+ return element;
795
+ }
796
+
797
+ /**
798
+ * The callback definition for toggleElClass.
799
+ *
800
+ * @callback Dom~PredicateCallback
801
+ * @param {Element} element
802
+ * The DOM element of the Component.
803
+ *
804
+ * @param {string} classToToggle
805
+ * The `className` that wants to be toggled
806
+ *
807
+ * @return {boolean|undefined}
808
+ * - If true the `classToToggle` will get added to `element`.
809
+ * - If false the `classToToggle` will get removed from `element`.
810
+ * - If undefined this callback will be ignored
811
+ */
812
+
813
+ /**
814
+ * Adds or removes a CSS class name on an element depending on an optional
815
+ * condition or the presence/absence of the class name.
816
+ *
817
+ * @param {Element} element
818
+ * The element to toggle a class name on.
819
+ *
820
+ * @param {string} classToToggle
821
+ * The class that should be toggled
822
+ *
823
+ * @param {boolean|PredicateCallback} [predicate]
824
+ * See the return value for {@link Dom~PredicateCallback}
825
+ *
826
+ * @return {Element}
827
+ * The element with a class that has been toggled.
828
+ */
829
+ function toggleClass(element, classToToggle, predicate) {
830
+
831
+ // This CANNOT use `classList` internally because IE11 does not support the
832
+ // second parameter to the `classList.toggle()` method! Which is fine because
833
+ // `classList` will be used by the add/remove functions.
834
+ var has = hasClass(element, classToToggle);
835
+
836
+ if (typeof predicate === 'function') {
837
+ predicate = predicate(element, classToToggle);
838
+ }
839
+
840
+ if (typeof predicate !== 'boolean') {
841
+ predicate = !has;
842
+ }
843
+
844
+ // If the necessary class operation matches the current state of the
845
+ // element, no action is required.
846
+ if (predicate === has) {
847
+ return;
848
+ }
849
+
850
+ if (predicate) {
851
+ addClass(element, classToToggle);
852
+ } else {
853
+ removeClass(element, classToToggle);
854
+ }
855
+
856
+ return element;
857
+ }
858
+
859
+ /**
860
+ * Apply attributes to an HTML element.
861
+ *
862
+ * @param {Element} el
863
+ * Element to add attributes to.
864
+ *
865
+ * @param {Object} [attributes]
866
+ * Attributes to be applied.
867
+ */
868
+ function setAttributes(el, attributes) {
869
+ Object.getOwnPropertyNames(attributes).forEach(function (attrName) {
870
+ var attrValue = attributes[attrName];
871
+
872
+ if (attrValue === null || typeof attrValue === 'undefined' || attrValue === false) {
873
+ el.removeAttribute(attrName);
874
+ } else {
875
+ el.setAttribute(attrName, attrValue === true ? '' : attrValue);
876
+ }
877
+ });
878
+ }
879
+
880
+ /**
881
+ * Get an element's attribute values, as defined on the HTML tag
882
+ * Attributes are not the same as properties. They're defined on the tag
883
+ * or with setAttribute (which shouldn't be used with HTML)
884
+ * This will return true or false for boolean attributes.
885
+ *
886
+ * @param {Element} tag
887
+ * Element from which to get tag attributes.
888
+ *
889
+ * @return {Object}
890
+ * All attributes of the element.
891
+ */
892
+ function getAttributes(tag) {
893
+ var obj = {};
894
+
895
+ // known boolean attributes
896
+ // we can check for matching boolean properties, but not all browsers
897
+ // and not all tags know about these attributes, so, we still want to check them manually
898
+ var knownBooleans = ',' + 'autoplay,controls,playsinline,loop,muted,default,defaultMuted' + ',';
899
+
900
+ if (tag && tag.attributes && tag.attributes.length > 0) {
901
+ var attrs = tag.attributes;
902
+
903
+ for (var i = attrs.length - 1; i >= 0; i--) {
904
+ var attrName = attrs[i].name;
905
+ var attrVal = attrs[i].value;
906
+
907
+ // check for known booleans
908
+ // the matching element property will return a value for typeof
909
+ if (typeof tag[attrName] === 'boolean' || knownBooleans.indexOf(',' + attrName + ',') !== -1) {
910
+ // the value of an included boolean attribute is typically an empty
911
+ // string ('') which would equal false if we just check for a false value.
912
+ // we also don't want support bad code like autoplay='false'
913
+ attrVal = attrVal !== null ? true : false;
914
+ }
915
+
916
+ obj[attrName] = attrVal;
917
+ }
918
+ }
919
+
920
+ return obj;
921
+ }
922
+
923
+ /**
924
+ * Get the value of an element's attribute
925
+ *
926
+ * @param {Element} el
927
+ * A DOM element
928
+ *
929
+ * @param {string} attribute
930
+ * Attribute to get the value of
931
+ *
932
+ * @return {string}
933
+ * value of the attribute
934
+ */
935
+ function getAttribute(el, attribute) {
936
+ return el.getAttribute(attribute);
937
+ }
938
+
939
+ /**
940
+ * Set the value of an element's attribute
941
+ *
942
+ * @param {Element} el
943
+ * A DOM element
944
+ *
945
+ * @param {string} attribute
946
+ * Attribute to set
947
+ *
948
+ * @param {string} value
949
+ * Value to set the attribute to
950
+ */
951
+ function setAttribute(el, attribute, value) {
952
+ el.setAttribute(attribute, value);
953
+ }
954
+
955
+ /**
956
+ * Remove an element's attribute
957
+ *
958
+ * @param {Element} el
959
+ * A DOM element
960
+ *
961
+ * @param {string} attribute
962
+ * Attribute to remove
963
+ */
964
+ function removeAttribute(el, attribute) {
965
+ el.removeAttribute(attribute);
966
+ }
967
+
968
+ /**
969
+ * Attempt to block the ability to select text while dragging controls
970
+ */
971
+ function blockTextSelection() {
972
+ document_1.body.focus();
973
+ document_1.onselectstart = function () {
974
+ return false;
975
+ };
976
+ }
977
+
978
+ /**
979
+ * Turn off text selection blocking
980
+ */
981
+ function unblockTextSelection() {
982
+ document_1.onselectstart = function () {
983
+ return true;
984
+ };
985
+ }
986
+
987
+ /**
988
+ * Identical to the native `getBoundingClientRect` function, but ensures that
989
+ * the method is supported at all (it is in all browsers we claim to support)
990
+ * and that the element is in the DOM before continuing.
991
+ *
992
+ * This wrapper function also shims properties which are not provided by some
993
+ * older browsers (namely, IE8).
994
+ *
995
+ * Additionally, some browsers do not support adding properties to a
996
+ * `ClientRect`/`DOMRect` object; so, we shallow-copy it with the standard
997
+ * properties (except `x` and `y` which are not widely supported). This helps
998
+ * avoid implementations where keys are non-enumerable.
999
+ *
1000
+ * @param {Element} el
1001
+ * Element whose `ClientRect` we want to calculate.
1002
+ *
1003
+ * @return {Object|undefined}
1004
+ * Always returns a plain
1005
+ */
1006
+ function getBoundingClientRect(el) {
1007
+ if (el && el.getBoundingClientRect && el.parentNode) {
1008
+ var rect = el.getBoundingClientRect();
1009
+ var result = {};
1010
+
1011
+ ['bottom', 'height', 'left', 'right', 'top', 'width'].forEach(function (k) {
1012
+ if (rect[k] !== undefined) {
1013
+ result[k] = rect[k];
1014
+ }
1015
+ });
1016
+
1017
+ if (!result.height) {
1018
+ result.height = parseFloat(computedStyle(el, 'height'));
1019
+ }
1020
+
1021
+ if (!result.width) {
1022
+ result.width = parseFloat(computedStyle(el, 'width'));
1023
+ }
1024
+
1025
+ return result;
1026
+ }
1027
+ }
1028
+
1029
+ /**
1030
+ * The postion of a DOM element on the page.
1031
+ *
1032
+ * @typedef {Object} module:dom~Position
1033
+ *
1034
+ * @property {number} left
1035
+ * Pixels to the left
1036
+ *
1037
+ * @property {number} top
1038
+ * Pixels on top
1039
+ */
1040
+
1041
+ /**
1042
+ * Offset Left.
1043
+ * getBoundingClientRect technique from
1044
+ * John Resig
1045
+ *
1046
+ * @see http://ejohn.org/blog/getboundingclientrect-is-awesome/
1047
+ *
1048
+ * @param {Element} el
1049
+ * Element from which to get offset
1050
+ *
1051
+ * @return {module:dom~Position}
1052
+ * The position of the element that was passed in.
1053
+ */
1054
+ function findPosition(el) {
1055
+ var box = void 0;
1056
+
1057
+ if (el.getBoundingClientRect && el.parentNode) {
1058
+ box = el.getBoundingClientRect();
1059
+ }
1060
+
1061
+ if (!box) {
1062
+ return {
1063
+ left: 0,
1064
+ top: 0
1065
+ };
1066
+ }
1067
+
1068
+ var docEl = document_1.documentElement;
1069
+ var body = document_1.body;
1070
+
1071
+ var clientLeft = docEl.clientLeft || body.clientLeft || 0;
1072
+ var scrollLeft = window_1.pageXOffset || body.scrollLeft;
1073
+ var left = box.left + scrollLeft - clientLeft;
1074
+
1075
+ var clientTop = docEl.clientTop || body.clientTop || 0;
1076
+ var scrollTop = window_1.pageYOffset || body.scrollTop;
1077
+ var top = box.top + scrollTop - clientTop;
1078
+
1079
+ // Android sometimes returns slightly off decimal values, so need to round
1080
+ return {
1081
+ left: Math.round(left),
1082
+ top: Math.round(top)
1083
+ };
1084
+ }
1085
+
1086
+ /**
1087
+ * x and y coordinates for a dom element or mouse pointer
1088
+ *
1089
+ * @typedef {Object} Dom~Coordinates
1090
+ *
1091
+ * @property {number} x
1092
+ * x coordinate in pixels
1093
+ *
1094
+ * @property {number} y
1095
+ * y coordinate in pixels
1096
+ */
1097
+
1098
+ /**
1099
+ * Get pointer position in element
1100
+ * Returns an object with x and y coordinates.
1101
+ * The base on the coordinates are the bottom left of the element.
1102
+ *
1103
+ * @param {Element} el
1104
+ * Element on which to get the pointer position on
1105
+ *
1106
+ * @param {EventTarget~Event} event
1107
+ * Event object
1108
+ *
1109
+ * @return {Dom~Coordinates}
1110
+ * A Coordinates object corresponding to the mouse position.
1111
+ *
1112
+ */
1113
+ function getPointerPosition(el, event) {
1114
+ var position = {};
1115
+ var box = findPosition(el);
1116
+ var boxW = el.offsetWidth;
1117
+ var boxH = el.offsetHeight;
1118
+
1119
+ var boxY = box.top;
1120
+ var boxX = box.left;
1121
+ var pageY = event.pageY;
1122
+ var pageX = event.pageX;
1123
+
1124
+ if (event.changedTouches) {
1125
+ pageX = event.changedTouches[0].pageX;
1126
+ pageY = event.changedTouches[0].pageY;
1127
+ }
1128
+
1129
+ position.y = Math.max(0, Math.min(1, (boxY - pageY + boxH) / boxH));
1130
+ position.x = Math.max(0, Math.min(1, (pageX - boxX) / boxW));
1131
+
1132
+ return position;
1133
+ }
1134
+
1135
+ /**
1136
+ * Determines, via duck typing, whether or not a value is a text node.
1137
+ *
1138
+ * @param {Mixed} value
1139
+ * Check if this value is a text node.
1140
+ *
1141
+ * @return {boolean}
1142
+ * - True if it is a text node
1143
+ * - False otherwise
1144
+ */
1145
+ function isTextNode(value) {
1146
+ return isObject(value) && value.nodeType === 3;
1147
+ }
1148
+
1149
+ /**
1150
+ * Empties the contents of an element.
1151
+ *
1152
+ * @param {Element} el
1153
+ * The element to empty children from
1154
+ *
1155
+ * @return {Element}
1156
+ * The element with no children
1157
+ */
1158
+ function emptyEl(el) {
1159
+ while (el.firstChild) {
1160
+ el.removeChild(el.firstChild);
1161
+ }
1162
+ return el;
1163
+ }
1164
+
1165
+ /**
1166
+ * Normalizes content for eventual insertion into the DOM.
1167
+ *
1168
+ * This allows a wide range of content definition methods, but protects
1169
+ * from falling into the trap of simply writing to `innerHTML`, which is
1170
+ * an XSS concern.
1171
+ *
1172
+ * The content for an element can be passed in multiple types and
1173
+ * combinations, whose behavior is as follows:
1174
+ *
1175
+ * @param {String|Element|TextNode|Array|Function} content
1176
+ * - String: Normalized into a text node.
1177
+ * - Element/TextNode: Passed through.
1178
+ * - Array: A one-dimensional array of strings, elements, nodes, or functions
1179
+ * (which return single strings, elements, or nodes).
1180
+ * - Function: If the sole argument, is expected to produce a string, element,
1181
+ * node, or array as defined above.
1182
+ *
1183
+ * @return {Array}
1184
+ * All of the content that was passed in normalized.
1185
+ */
1186
+ function normalizeContent(content) {
1187
+
1188
+ // First, invoke content if it is a function. If it produces an array,
1189
+ // that needs to happen before normalization.
1190
+ if (typeof content === 'function') {
1191
+ content = content();
1192
+ }
1193
+
1194
+ // Next up, normalize to an array, so one or many items can be normalized,
1195
+ // filtered, and returned.
1196
+ return (Array.isArray(content) ? content : [content]).map(function (value) {
1197
+
1198
+ // First, invoke value if it is a function to produce a new value,
1199
+ // which will be subsequently normalized to a Node of some kind.
1200
+ if (typeof value === 'function') {
1201
+ value = value();
1202
+ }
1203
+
1204
+ if (isEl(value) || isTextNode(value)) {
1205
+ return value;
1206
+ }
1207
+
1208
+ if (typeof value === 'string' && /\S/.test(value)) {
1209
+ return document_1.createTextNode(value);
1210
+ }
1211
+ }).filter(function (value) {
1212
+ return value;
1213
+ });
1214
+ }
1215
+
1216
+ /**
1217
+ * Normalizes and appends content to an element.
1218
+ *
1219
+ * @param {Element} el
1220
+ * Element to append normalized content to.
1221
+ *
1222
+ *
1223
+ * @param {String|Element|TextNode|Array|Function} content
1224
+ * See the `content` argument of {@link dom:normalizeContent}
1225
+ *
1226
+ * @return {Element}
1227
+ * The element with appended normalized content.
1228
+ */
1229
+ function appendContent(el, content) {
1230
+ normalizeContent(content).forEach(function (node) {
1231
+ return el.appendChild(node);
1232
+ });
1233
+ return el;
1234
+ }
1235
+
1236
+ /**
1237
+ * Normalizes and inserts content into an element; this is identical to
1238
+ * `appendContent()`, except it empties the element first.
1239
+ *
1240
+ * @param {Element} el
1241
+ * Element to insert normalized content into.
1242
+ *
1243
+ * @param {String|Element|TextNode|Array|Function} content
1244
+ * See the `content` argument of {@link dom:normalizeContent}
1245
+ *
1246
+ * @return {Element}
1247
+ * The element with inserted normalized content.
1248
+ *
1249
+ */
1250
+ function insertContent(el, content) {
1251
+ return appendContent(emptyEl(el), content);
1252
+ }
1253
+
1254
+ /**
1255
+ * Check if event was a single left click
1256
+ *
1257
+ * @param {EventTarget~Event} event
1258
+ * Event object
1259
+ *
1260
+ * @return {boolean}
1261
+ * - True if a left click
1262
+ * - False if not a left click
1263
+ */
1264
+ function isSingleLeftClick(event) {
1265
+ // Note: if you create something draggable, be sure to
1266
+ // call it on both `mousedown` and `mousemove` event,
1267
+ // otherwise `mousedown` should be enough for a button
1268
+
1269
+ if (event.button === undefined && event.buttons === undefined) {
1270
+ // Why do we need `buttons` ?
1271
+ // Because, middle mouse sometimes have this:
1272
+ // e.button === 0 and e.buttons === 4
1273
+ // Furthermore, we want to prevent combination click, something like
1274
+ // HOLD middlemouse then left click, that would be
1275
+ // e.button === 0, e.buttons === 5
1276
+ // just `button` is not gonna work
1277
+
1278
+ // Alright, then what this block does ?
1279
+ // this is for chrome `simulate mobile devices`
1280
+ // I want to support this as well
1281
+
1282
+ return true;
1283
+ }
1284
+
1285
+ if (event.button === 0 && event.buttons === undefined) {
1286
+ // Touch screen, sometimes on some specific device, `buttons`
1287
+ // doesn't have anything (safari on ios, blackberry...)
1288
+
1289
+ return true;
1290
+ }
1291
+
1292
+ if (event.button !== 0 || event.buttons !== 1) {
1293
+ // This is the reason we have those if else block above
1294
+ // if any special case we can catch and let it slide
1295
+ // we do it above, when get to here, this definitely
1296
+ // is-not-left-click
1297
+
1298
+ return false;
1299
+ }
1300
+
1301
+ return true;
1302
+ }
1303
+
1304
+ /**
1305
+ * Finds a single DOM element matching `selector` within the optional
1306
+ * `context` of another DOM element (defaulting to `document`).
1307
+ *
1308
+ * @param {string} selector
1309
+ * A valid CSS selector, which will be passed to `querySelector`.
1310
+ *
1311
+ * @param {Element|String} [context=document]
1312
+ * A DOM element within which to query. Can also be a selector
1313
+ * string in which case the first matching element will be used
1314
+ * as context. If missing (or no element matches selector), falls
1315
+ * back to `document`.
1316
+ *
1317
+ * @return {Element|null}
1318
+ * The element that was found or null.
1319
+ */
1320
+ var $ = createQuerier('querySelector');
1321
+
1322
+ /**
1323
+ * Finds a all DOM elements matching `selector` within the optional
1324
+ * `context` of another DOM element (defaulting to `document`).
1325
+ *
1326
+ * @param {string} selector
1327
+ * A valid CSS selector, which will be passed to `querySelectorAll`.
1328
+ *
1329
+ * @param {Element|String} [context=document]
1330
+ * A DOM element within which to query. Can also be a selector
1331
+ * string in which case the first matching element will be used
1332
+ * as context. If missing (or no element matches selector), falls
1333
+ * back to `document`.
1334
+ *
1335
+ * @return {NodeList}
1336
+ * A element list of elements that were found. Will be empty if none were found.
1337
+ *
1338
+ */
1339
+ var $$ = createQuerier('querySelectorAll');
1340
+
1341
+ var Dom = /*#__PURE__*/Object.freeze({
1342
+ isReal: isReal,
1343
+ isEl: isEl,
1344
+ isInFrame: isInFrame,
1345
+ createEl: createEl,
1346
+ textContent: textContent,
1347
+ prependTo: prependTo,
1348
+ hasClass: hasClass,
1349
+ addClass: addClass,
1350
+ removeClass: removeClass,
1351
+ toggleClass: toggleClass,
1352
+ setAttributes: setAttributes,
1353
+ getAttributes: getAttributes,
1354
+ getAttribute: getAttribute,
1355
+ setAttribute: setAttribute,
1356
+ removeAttribute: removeAttribute,
1357
+ blockTextSelection: blockTextSelection,
1358
+ unblockTextSelection: unblockTextSelection,
1359
+ getBoundingClientRect: getBoundingClientRect,
1360
+ findPosition: findPosition,
1361
+ getPointerPosition: getPointerPosition,
1362
+ isTextNode: isTextNode,
1363
+ emptyEl: emptyEl,
1364
+ normalizeContent: normalizeContent,
1365
+ appendContent: appendContent,
1366
+ insertContent: insertContent,
1367
+ isSingleLeftClick: isSingleLeftClick,
1368
+ $: $,
1369
+ $$: $$
1370
+ });
1371
+
1372
+ /**
1373
+ * @file guid.js
1374
+ * @module guid
1375
+ */
1376
+
1377
+ /**
1378
+ * Unique ID for an element or function
1379
+ * @type {Number}
1380
+ */
1381
+ var _guid = 1;
1382
+
1383
+ /**
1384
+ * Get a unique auto-incrementing ID by number that has not been returned before.
1385
+ *
1386
+ * @return {number}
1387
+ * A new unique ID.
1388
+ */
1389
+ function newGUID() {
1390
+ return _guid++;
1391
+ }
1392
+
1393
+ /**
1394
+ * @file dom-data.js
1395
+ * @module dom-data
1396
+ */
1397
+
1398
+ /**
1399
+ * Element Data Store.
1400
+ *
1401
+ * Allows for binding data to an element without putting it directly on the
1402
+ * element. Ex. Event listeners are stored here.
1403
+ * (also from jsninja.com, slightly modified and updated for closure compiler)
1404
+ *
1405
+ * @type {Object}
1406
+ * @private
1407
+ */
1408
+ var elData = {};
1409
+
1410
+ /*
1411
+ * Unique attribute name to store an element's guid in
1412
+ *
1413
+ * @type {String}
1414
+ * @constant
1415
+ * @private
1416
+ */
1417
+ var elIdAttr = 'vdata' + new Date().getTime();
1418
+
1419
+ /**
1420
+ * Returns the cache object where data for an element is stored
1421
+ *
1422
+ * @param {Element} el
1423
+ * Element to store data for.
1424
+ *
1425
+ * @return {Object}
1426
+ * The cache object for that el that was passed in.
1427
+ */
1428
+ function getData(el) {
1429
+ var id = el[elIdAttr];
1430
+
1431
+ if (!id) {
1432
+ id = el[elIdAttr] = newGUID();
1433
+ }
1434
+
1435
+ if (!elData[id]) {
1436
+ elData[id] = {};
1437
+ }
1438
+
1439
+ return elData[id];
1440
+ }
1441
+
1442
+ /**
1443
+ * Returns whether or not an element has cached data
1444
+ *
1445
+ * @param {Element} el
1446
+ * Check if this element has cached data.
1447
+ *
1448
+ * @return {boolean}
1449
+ * - True if the DOM element has cached data.
1450
+ * - False otherwise.
1451
+ */
1452
+ function hasData(el) {
1453
+ var id = el[elIdAttr];
1454
+
1455
+ if (!id) {
1456
+ return false;
1457
+ }
1458
+
1459
+ return !!Object.getOwnPropertyNames(elData[id]).length;
1460
+ }
1461
+
1462
+ /**
1463
+ * Delete data for the element from the cache and the guid attr from getElementById
1464
+ *
1465
+ * @param {Element} el
1466
+ * Remove cached data for this element.
1467
+ */
1468
+ function removeData(el) {
1469
+ var id = el[elIdAttr];
1470
+
1471
+ if (!id) {
1472
+ return;
1473
+ }
1474
+
1475
+ // Remove all stored data
1476
+ delete elData[id];
1477
+
1478
+ // Remove the elIdAttr property from the DOM node
1479
+ try {
1480
+ delete el[elIdAttr];
1481
+ } catch (e) {
1482
+ if (el.removeAttribute) {
1483
+ el.removeAttribute(elIdAttr);
1484
+ } else {
1485
+ // IE doesn't appear to support removeAttribute on the document element
1486
+ el[elIdAttr] = null;
1487
+ }
1488
+ }
1489
+ }
1490
+
1491
+ /**
1492
+ * @file events.js. An Event System (John Resig - Secrets of a JS Ninja http://jsninja.com/)
1493
+ * (Original book version wasn't completely usable, so fixed some things and made Closure Compiler compatible)
1494
+ * This should work very similarly to jQuery's events, however it's based off the book version which isn't as
1495
+ * robust as jquery's, so there's probably some differences.
1496
+ *
1497
+ * @module events
1498
+ */
1499
+
1500
+ /**
1501
+ * Clean up the listener cache and dispatchers
1502
+ *
1503
+ * @param {Element|Object} elem
1504
+ * Element to clean up
1505
+ *
1506
+ * @param {string} type
1507
+ * Type of event to clean up
1508
+ */
1509
+ function _cleanUpEvents(elem, type) {
1510
+ var data = getData(elem);
1511
+
1512
+ // Remove the events of a particular type if there are none left
1513
+ if (data.handlers[type].length === 0) {
1514
+ delete data.handlers[type];
1515
+ // data.handlers[type] = null;
1516
+ // Setting to null was causing an error with data.handlers
1517
+
1518
+ // Remove the meta-handler from the element
1519
+ if (elem.removeEventListener) {
1520
+ elem.removeEventListener(type, data.dispatcher, false);
1521
+ } else if (elem.detachEvent) {
1522
+ elem.detachEvent('on' + type, data.dispatcher);
1523
+ }
1524
+ }
1525
+
1526
+ // Remove the events object if there are no types left
1527
+ if (Object.getOwnPropertyNames(data.handlers).length <= 0) {
1528
+ delete data.handlers;
1529
+ delete data.dispatcher;
1530
+ delete data.disabled;
1531
+ }
1532
+
1533
+ // Finally remove the element data if there is no data left
1534
+ if (Object.getOwnPropertyNames(data).length === 0) {
1535
+ removeData(elem);
1536
+ }
1537
+ }
1538
+
1539
+ /**
1540
+ * Loops through an array of event types and calls the requested method for each type.
1541
+ *
1542
+ * @param {Function} fn
1543
+ * The event method we want to use.
1544
+ *
1545
+ * @param {Element|Object} elem
1546
+ * Element or object to bind listeners to
1547
+ *
1548
+ * @param {string} type
1549
+ * Type of event to bind to.
1550
+ *
1551
+ * @param {EventTarget~EventListener} callback
1552
+ * Event listener.
1553
+ */
1554
+ function _handleMultipleEvents(fn, elem, types, callback) {
1555
+ types.forEach(function (type) {
1556
+ // Call the event method for each one of the types
1557
+ fn(elem, type, callback);
1558
+ });
1559
+ }
1560
+
1561
+ /**
1562
+ * Fix a native event to have standard property values
1563
+ *
1564
+ * @param {Object} event
1565
+ * Event object to fix.
1566
+ *
1567
+ * @return {Object}
1568
+ * Fixed event object.
1569
+ */
1570
+ function fixEvent(event) {
1571
+
1572
+ function returnTrue() {
1573
+ return true;
1574
+ }
1575
+
1576
+ function returnFalse() {
1577
+ return false;
1578
+ }
1579
+
1580
+ // Test if fixing up is needed
1581
+ // Used to check if !event.stopPropagation instead of isPropagationStopped
1582
+ // But native events return true for stopPropagation, but don't have
1583
+ // other expected methods like isPropagationStopped. Seems to be a problem
1584
+ // with the Javascript Ninja code. So we're just overriding all events now.
1585
+ if (!event || !event.isPropagationStopped) {
1586
+ var old = event || window_1.event;
1587
+
1588
+ event = {};
1589
+ // Clone the old object so that we can modify the values event = {};
1590
+ // IE8 Doesn't like when you mess with native event properties
1591
+ // Firefox returns false for event.hasOwnProperty('type') and other props
1592
+ // which makes copying more difficult.
1593
+ // TODO: Probably best to create a whitelist of event props
1594
+ for (var key in old) {
1595
+ // Safari 6.0.3 warns you if you try to copy deprecated layerX/Y
1596
+ // Chrome warns you if you try to copy deprecated keyboardEvent.keyLocation
1597
+ // and webkitMovementX/Y
1598
+ if (key !== 'layerX' && key !== 'layerY' && key !== 'keyLocation' && key !== 'webkitMovementX' && key !== 'webkitMovementY') {
1599
+ // Chrome 32+ warns if you try to copy deprecated returnValue, but
1600
+ // we still want to if preventDefault isn't supported (IE8).
1601
+ if (!(key === 'returnValue' && old.preventDefault)) {
1602
+ event[key] = old[key];
1603
+ }
1604
+ }
1605
+ }
1606
+
1607
+ // The event occurred on this element
1608
+ if (!event.target) {
1609
+ event.target = event.srcElement || document_1;
1610
+ }
1611
+
1612
+ // Handle which other element the event is related to
1613
+ if (!event.relatedTarget) {
1614
+ event.relatedTarget = event.fromElement === event.target ? event.toElement : event.fromElement;
1615
+ }
1616
+
1617
+ // Stop the default browser action
1618
+ event.preventDefault = function () {
1619
+ if (old.preventDefault) {
1620
+ old.preventDefault();
1621
+ }
1622
+ event.returnValue = false;
1623
+ old.returnValue = false;
1624
+ event.defaultPrevented = true;
1625
+ };
1626
+
1627
+ event.defaultPrevented = false;
1628
+
1629
+ // Stop the event from bubbling
1630
+ event.stopPropagation = function () {
1631
+ if (old.stopPropagation) {
1632
+ old.stopPropagation();
1633
+ }
1634
+ event.cancelBubble = true;
1635
+ old.cancelBubble = true;
1636
+ event.isPropagationStopped = returnTrue;
1637
+ };
1638
+
1639
+ event.isPropagationStopped = returnFalse;
1640
+
1641
+ // Stop the event from bubbling and executing other handlers
1642
+ event.stopImmediatePropagation = function () {
1643
+ if (old.stopImmediatePropagation) {
1644
+ old.stopImmediatePropagation();
1645
+ }
1646
+ event.isImmediatePropagationStopped = returnTrue;
1647
+ event.stopPropagation();
1648
+ };
1649
+
1650
+ event.isImmediatePropagationStopped = returnFalse;
1651
+
1652
+ // Handle mouse position
1653
+ if (event.clientX !== null && event.clientX !== undefined) {
1654
+ var doc = document_1.documentElement;
1655
+ var body = document_1.body;
1656
+
1657
+ event.pageX = event.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc && doc.clientLeft || body && body.clientLeft || 0);
1658
+ event.pageY = event.clientY + (doc && doc.scrollTop || body && body.scrollTop || 0) - (doc && doc.clientTop || body && body.clientTop || 0);
1659
+ }
1660
+
1661
+ // Handle key presses
1662
+ event.which = event.charCode || event.keyCode;
1663
+
1664
+ // Fix button for mouse clicks:
1665
+ // 0 == left; 1 == middle; 2 == right
1666
+ if (event.button !== null && event.button !== undefined) {
1667
+
1668
+ // The following is disabled because it does not pass videojs-standard
1669
+ // and... yikes.
1670
+ /* eslint-disable */
1671
+ event.button = event.button & 1 ? 0 : event.button & 4 ? 1 : event.button & 2 ? 2 : 0;
1672
+ /* eslint-enable */
1673
+ }
1674
+ }
1675
+
1676
+ // Returns fixed-up instance
1677
+ return event;
1678
+ }
1679
+
1680
+ /**
1681
+ * Whether passive event listeners are supported
1682
+ */
1683
+ var _supportsPassive = false;
1684
+
1685
+ (function () {
1686
+ try {
1687
+ var opts = Object.defineProperty({}, 'passive', {
1688
+ get: function get() {
1689
+ _supportsPassive = true;
1690
+ }
1691
+ });
1692
+
1693
+ window_1.addEventListener('test', null, opts);
1694
+ window_1.removeEventListener('test', null, opts);
1695
+ } catch (e) {
1696
+ // disregard
1697
+ }
1698
+ })();
1699
+
1700
+ /**
1701
+ * Touch events Chrome expects to be passive
1702
+ */
1703
+ var passiveEvents = ['touchstart', 'touchmove'];
1704
+
1705
+ /**
1706
+ * Add an event listener to element
1707
+ * It stores the handler function in a separate cache object
1708
+ * and adds a generic handler to the element's event,
1709
+ * along with a unique id (guid) to the element.
1710
+ *
1711
+ * @param {Element|Object} elem
1712
+ * Element or object to bind listeners to
1713
+ *
1714
+ * @param {string|string[]} type
1715
+ * Type of event to bind to.
1716
+ *
1717
+ * @param {EventTarget~EventListener} fn
1718
+ * Event listener.
1719
+ */
1720
+ function on(elem, type, fn) {
1721
+ if (Array.isArray(type)) {
1722
+ return _handleMultipleEvents(on, elem, type, fn);
1723
+ }
1724
+
1725
+ var data = getData(elem);
1726
+
1727
+ // We need a place to store all our handler data
1728
+ if (!data.handlers) {
1729
+ data.handlers = {};
1730
+ }
1731
+
1732
+ if (!data.handlers[type]) {
1733
+ data.handlers[type] = [];
1734
+ }
1735
+
1736
+ if (!fn.guid) {
1737
+ fn.guid = newGUID();
1738
+ }
1739
+
1740
+ data.handlers[type].push(fn);
1741
+
1742
+ if (!data.dispatcher) {
1743
+ data.disabled = false;
1744
+
1745
+ data.dispatcher = function (event, hash) {
1746
+
1747
+ if (data.disabled) {
1748
+ return;
1749
+ }
1750
+
1751
+ event = fixEvent(event);
1752
+
1753
+ var handlers = data.handlers[event.type];
1754
+
1755
+ if (handlers) {
1756
+ // Copy handlers so if handlers are added/removed during the process it doesn't throw everything off.
1757
+ var handlersCopy = handlers.slice(0);
1758
+
1759
+ for (var m = 0, n = handlersCopy.length; m < n; m++) {
1760
+ if (event.isImmediatePropagationStopped()) {
1761
+ break;
1762
+ } else {
1763
+ try {
1764
+ handlersCopy[m].call(elem, event, hash);
1765
+ } catch (e) {
1766
+ log$1.error(e);
1767
+ }
1768
+ }
1769
+ }
1770
+ }
1771
+ };
1772
+ }
1773
+
1774
+ if (data.handlers[type].length === 1) {
1775
+ if (elem.addEventListener) {
1776
+ var options = false;
1777
+
1778
+ if (_supportsPassive && passiveEvents.indexOf(type) > -1) {
1779
+ options = { passive: true };
1780
+ }
1781
+ elem.addEventListener(type, data.dispatcher, options);
1782
+ } else if (elem.attachEvent) {
1783
+ elem.attachEvent('on' + type, data.dispatcher);
1784
+ }
1785
+ }
1786
+ }
1787
+
1788
+ /**
1789
+ * Removes event listeners from an element
1790
+ *
1791
+ * @param {Element|Object} elem
1792
+ * Object to remove listeners from.
1793
+ *
1794
+ * @param {string|string[]} [type]
1795
+ * Type of listener to remove. Don't include to remove all events from element.
1796
+ *
1797
+ * @param {EventTarget~EventListener} [fn]
1798
+ * Specific listener to remove. Don't include to remove listeners for an event
1799
+ * type.
1800
+ */
1801
+ function off(elem, type, fn) {
1802
+ // Don't want to add a cache object through getElData if not needed
1803
+ if (!hasData(elem)) {
1804
+ return;
1805
+ }
1806
+
1807
+ var data = getData(elem);
1808
+
1809
+ // If no events exist, nothing to unbind
1810
+ if (!data.handlers) {
1811
+ return;
1812
+ }
1813
+
1814
+ if (Array.isArray(type)) {
1815
+ return _handleMultipleEvents(off, elem, type, fn);
1816
+ }
1817
+
1818
+ // Utility function
1819
+ var removeType = function removeType(el, t) {
1820
+ data.handlers[t] = [];
1821
+ _cleanUpEvents(el, t);
1822
+ };
1823
+
1824
+ // Are we removing all bound events?
1825
+ if (type === undefined) {
1826
+ for (var t in data.handlers) {
1827
+ if (Object.prototype.hasOwnProperty.call(data.handlers || {}, t)) {
1828
+ removeType(elem, t);
1829
+ }
1830
+ }
1831
+ return;
1832
+ }
1833
+
1834
+ var handlers = data.handlers[type];
1835
+
1836
+ // If no handlers exist, nothing to unbind
1837
+ if (!handlers) {
1838
+ return;
1839
+ }
1840
+
1841
+ // If no listener was provided, remove all listeners for type
1842
+ if (!fn) {
1843
+ removeType(elem, type);
1844
+ return;
1845
+ }
1846
+
1847
+ // We're only removing a single handler
1848
+ if (fn.guid) {
1849
+ for (var n = 0; n < handlers.length; n++) {
1850
+ if (handlers[n].guid === fn.guid) {
1851
+ handlers.splice(n--, 1);
1852
+ }
1853
+ }
1854
+ }
1855
+
1856
+ _cleanUpEvents(elem, type);
1857
+ }
1858
+
1859
+ /**
1860
+ * Trigger an event for an element
1861
+ *
1862
+ * @param {Element|Object} elem
1863
+ * Element to trigger an event on
1864
+ *
1865
+ * @param {EventTarget~Event|string} event
1866
+ * A string (the type) or an event object with a type attribute
1867
+ *
1868
+ * @param {Object} [hash]
1869
+ * data hash to pass along with the event
1870
+ *
1871
+ * @return {boolean|undefined}
1872
+ * - Returns the opposite of `defaultPrevented` if default was prevented
1873
+ * - Otherwise returns undefined
1874
+ */
1875
+ function trigger(elem, event, hash) {
1876
+ // Fetches element data and a reference to the parent (for bubbling).
1877
+ // Don't want to add a data object to cache for every parent,
1878
+ // so checking hasElData first.
1879
+ var elemData = hasData(elem) ? getData(elem) : {};
1880
+ var parent = elem.parentNode || elem.ownerDocument;
1881
+ // type = event.type || event,
1882
+ // handler;
1883
+
1884
+ // If an event name was passed as a string, creates an event out of it
1885
+ if (typeof event === 'string') {
1886
+ event = { type: event, target: elem };
1887
+ } else if (!event.target) {
1888
+ event.target = elem;
1889
+ }
1890
+
1891
+ // Normalizes the event properties.
1892
+ event = fixEvent(event);
1893
+
1894
+ // If the passed element has a dispatcher, executes the established handlers.
1895
+ if (elemData.dispatcher) {
1896
+ elemData.dispatcher.call(elem, event, hash);
1897
+ }
1898
+
1899
+ // Unless explicitly stopped or the event does not bubble (e.g. media events)
1900
+ // recursively calls this function to bubble the event up the DOM.
1901
+ if (parent && !event.isPropagationStopped() && event.bubbles === true) {
1902
+ trigger.call(null, parent, event, hash);
1903
+
1904
+ // If at the top of the DOM, triggers the default action unless disabled.
1905
+ } else if (!parent && !event.defaultPrevented) {
1906
+ var targetData = getData(event.target);
1907
+
1908
+ // Checks if the target has a default action for this event.
1909
+ if (event.target[event.type]) {
1910
+ // Temporarily disables event dispatching on the target as we have already executed the handler.
1911
+ targetData.disabled = true;
1912
+ // Executes the default action.
1913
+ if (typeof event.target[event.type] === 'function') {
1914
+ event.target[event.type]();
1915
+ }
1916
+ // Re-enables event dispatching.
1917
+ targetData.disabled = false;
1918
+ }
1919
+ }
1920
+
1921
+ // Inform the triggerer if the default was prevented by returning false
1922
+ return !event.defaultPrevented;
1923
+ }
1924
+
1925
+ /**
1926
+ * Trigger a listener only once for an event
1927
+ *
1928
+ * @param {Element|Object} elem
1929
+ * Element or object to bind to.
1930
+ *
1931
+ * @param {string|string[]} type
1932
+ * Name/type of event
1933
+ *
1934
+ * @param {Event~EventListener} fn
1935
+ * Event Listener function
1936
+ */
1937
+ function one(elem, type, fn) {
1938
+ if (Array.isArray(type)) {
1939
+ return _handleMultipleEvents(one, elem, type, fn);
1940
+ }
1941
+ var func = function func() {
1942
+ off(elem, type, func);
1943
+ fn.apply(this, arguments);
1944
+ };
1945
+
1946
+ // copy the guid to the new function so it can removed using the original function's ID
1947
+ func.guid = fn.guid = fn.guid || newGUID();
1948
+ on(elem, type, func);
1949
+ }
1950
+
1951
+ var Events = /*#__PURE__*/Object.freeze({
1952
+ fixEvent: fixEvent,
1953
+ on: on,
1954
+ off: off,
1955
+ trigger: trigger,
1956
+ one: one
1957
+ });
1958
+
1959
+ /**
1960
+ * @file setup.js - Functions for setting up a player without
1961
+ * user interaction based on the data-setup `attribute` of the video tag.
1962
+ *
1963
+ * @module setup
1964
+ */
1965
+
1966
+ var _windowLoaded = false;
1967
+ var videojs = void 0;
1968
+
1969
+ /**
1970
+ * Set up any tags that have a data-setup `attribute` when the player is started.
1971
+ */
1972
+ var autoSetup = function autoSetup() {
1973
+
1974
+ // Protect against breakage in non-browser environments and check global autoSetup option.
1975
+ if (!isReal() || videojs.options.autoSetup === false) {
1976
+ return;
1977
+ }
1978
+
1979
+ var vids = Array.prototype.slice.call(document_1.getElementsByTagName('video'));
1980
+ var audios = Array.prototype.slice.call(document_1.getElementsByTagName('audio'));
1981
+ var divs = Array.prototype.slice.call(document_1.getElementsByTagName('video-js'));
1982
+ var mediaEls = vids.concat(audios, divs);
1983
+
1984
+ // Check if any media elements exist
1985
+ if (mediaEls && mediaEls.length > 0) {
1986
+
1987
+ for (var i = 0, e = mediaEls.length; i < e; i++) {
1988
+ var mediaEl = mediaEls[i];
1989
+
1990
+ // Check if element exists, has getAttribute func.
1991
+ if (mediaEl && mediaEl.getAttribute) {
1992
+
1993
+ // Make sure this player hasn't already been set up.
1994
+ if (mediaEl.player === undefined) {
1995
+ var options = mediaEl.getAttribute('data-setup');
1996
+
1997
+ // Check if data-setup attr exists.
1998
+ // We only auto-setup if they've added the data-setup attr.
1999
+ if (options !== null) {
2000
+ // Create new video.js instance.
2001
+ videojs(mediaEl);
2002
+ }
2003
+ }
2004
+
2005
+ // If getAttribute isn't defined, we need to wait for the DOM.
2006
+ } else {
2007
+ autoSetupTimeout(1);
2008
+ break;
2009
+ }
2010
+ }
2011
+
2012
+ // No videos were found, so keep looping unless page is finished loading.
2013
+ } else if (!_windowLoaded) {
2014
+ autoSetupTimeout(1);
2015
+ }
2016
+ };
2017
+
2018
+ /**
2019
+ * Wait until the page is loaded before running autoSetup. This will be called in
2020
+ * autoSetup if `hasLoaded` returns false.
2021
+ *
2022
+ * @param {number} wait
2023
+ * How long to wait in ms
2024
+ *
2025
+ * @param {module:videojs} [vjs]
2026
+ * The videojs library function
2027
+ */
2028
+ function autoSetupTimeout(wait, vjs) {
2029
+ if (vjs) {
2030
+ videojs = vjs;
2031
+ }
2032
+
2033
+ window_1.setTimeout(autoSetup, wait);
2034
+ }
2035
+
2036
+ if (isReal() && document_1.readyState === 'complete') {
2037
+ _windowLoaded = true;
2038
+ } else {
2039
+ /**
2040
+ * Listen for the load event on window, and set _windowLoaded to true.
2041
+ *
2042
+ * @listens load
2043
+ */
2044
+ one(window_1, 'load', function () {
2045
+ _windowLoaded = true;
2046
+ });
2047
+ }
2048
+
2049
+ /**
2050
+ * @file stylesheet.js
2051
+ * @module stylesheet
2052
+ */
2053
+
2054
+ /**
2055
+ * Create a DOM syle element given a className for it.
2056
+ *
2057
+ * @param {string} className
2058
+ * The className to add to the created style element.
2059
+ *
2060
+ * @return {Element}
2061
+ * The element that was created.
2062
+ */
2063
+ var createStyleElement = function createStyleElement(className) {
2064
+ var style = document_1.createElement('style');
2065
+
2066
+ style.className = className;
2067
+
2068
+ return style;
2069
+ };
2070
+
2071
+ /**
2072
+ * Add text to a DOM element.
2073
+ *
2074
+ * @param {Element} el
2075
+ * The Element to add text content to.
2076
+ *
2077
+ * @param {string} content
2078
+ * The text to add to the element.
2079
+ */
2080
+ var setTextContent = function setTextContent(el, content) {
2081
+ if (el.styleSheet) {
2082
+ el.styleSheet.cssText = content;
2083
+ } else {
2084
+ el.textContent = content;
2085
+ }
2086
+ };
2087
+
2088
+ /**
2089
+ * @file fn.js
2090
+ * @module fn
2091
+ */
2092
+
2093
+ /**
2094
+ * Bind (a.k.a proxy or Context). A simple method for changing the context of a function
2095
+ * It also stores a unique id on the function so it can be easily removed from events.
2096
+ *
2097
+ * @param {Mixed} context
2098
+ * The object to bind as scope.
2099
+ *
2100
+ * @param {Function} fn
2101
+ * The function to be bound to a scope.
2102
+ *
2103
+ * @param {number} [uid]
2104
+ * An optional unique ID for the function to be set
2105
+ *
2106
+ * @return {Function}
2107
+ * The new function that will be bound into the context given
2108
+ */
2109
+ var bind = function bind(context, fn, uid) {
2110
+ // Make sure the function has a unique ID
2111
+ if (!fn.guid) {
2112
+ fn.guid = newGUID();
2113
+ }
2114
+
2115
+ // Create the new function that changes the context
2116
+ var bound = function bound() {
2117
+ return fn.apply(context, arguments);
2118
+ };
2119
+
2120
+ // Allow for the ability to individualize this function
2121
+ // Needed in the case where multiple objects might share the same prototype
2122
+ // IF both items add an event listener with the same function, then you try to remove just one
2123
+ // it will remove both because they both have the same guid.
2124
+ // when using this, you need to use the bind method when you remove the listener as well.
2125
+ // currently used in text tracks
2126
+ bound.guid = uid ? uid + '_' + fn.guid : fn.guid;
2127
+
2128
+ return bound;
2129
+ };
2130
+
2131
+ /**
2132
+ * Wraps the given function, `fn`, with a new function that only invokes `fn`
2133
+ * at most once per every `wait` milliseconds.
2134
+ *
2135
+ * @param {Function} fn
2136
+ * The function to be throttled.
2137
+ *
2138
+ * @param {Number} wait
2139
+ * The number of milliseconds by which to throttle.
2140
+ *
2141
+ * @return {Function}
2142
+ */
2143
+ var throttle = function throttle(fn, wait) {
2144
+ var last = Date.now();
2145
+
2146
+ var throttled = function throttled() {
2147
+ var now = Date.now();
2148
+
2149
+ if (now - last >= wait) {
2150
+ fn.apply(undefined, arguments);
2151
+ last = now;
2152
+ }
2153
+ };
2154
+
2155
+ return throttled;
2156
+ };
2157
+
2158
+ /**
2159
+ * Creates a debounced function that delays invoking `func` until after `wait`
2160
+ * milliseconds have elapsed since the last time the debounced function was
2161
+ * invoked.
2162
+ *
2163
+ * Inspired by lodash and underscore implementations.
2164
+ *
2165
+ * @param {Function} func
2166
+ * The function to wrap with debounce behavior.
2167
+ *
2168
+ * @param {number} wait
2169
+ * The number of milliseconds to wait after the last invocation.
2170
+ *
2171
+ * @param {boolean} [immediate]
2172
+ * Whether or not to invoke the function immediately upon creation.
2173
+ *
2174
+ * @param {Object} [context=window]
2175
+ * The "context" in which the debounced function should debounce. For
2176
+ * example, if this function should be tied to a Video.js player,
2177
+ * the player can be passed here. Alternatively, defaults to the
2178
+ * global `window` object.
2179
+ *
2180
+ * @return {Function}
2181
+ * A debounced function.
2182
+ */
2183
+ var debounce = function debounce(func, wait, immediate) {
2184
+ var context = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : window_1;
2185
+
2186
+ var timeout = void 0;
2187
+
2188
+ /* eslint-disable consistent-this */
2189
+ return function () {
2190
+ var self = this;
2191
+ var args = arguments;
2192
+
2193
+ var _later = function later() {
2194
+ timeout = null;
2195
+ _later = null;
2196
+ if (!immediate) {
2197
+ func.apply(self, args);
2198
+ }
2199
+ };
2200
+
2201
+ if (!timeout && immediate) {
2202
+ func.apply(self, args);
2203
+ }
2204
+
2205
+ context.clearTimeout(timeout);
2206
+ timeout = context.setTimeout(_later, wait);
2207
+ };
2208
+ /* eslint-enable consistent-this */
2209
+ };
2210
+
2211
+ /**
2212
+ * @file src/js/event-target.js
2213
+ */
2214
+
2215
+ /**
2216
+ * `EventTarget` is a class that can have the same API as the DOM `EventTarget`. It
2217
+ * adds shorthand functions that wrap around lengthy functions. For example:
2218
+ * the `on` function is a wrapper around `addEventListener`.
2219
+ *
2220
+ * @see [EventTarget Spec]{@link https://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-EventTarget}
2221
+ * @class EventTarget
2222
+ */
2223
+ var EventTarget = function EventTarget() {};
2224
+
2225
+ /**
2226
+ * A Custom DOM event.
2227
+ *
2228
+ * @typedef {Object} EventTarget~Event
2229
+ * @see [Properties]{@link https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent}
2230
+ */
2231
+
2232
+ /**
2233
+ * All event listeners should follow the following format.
2234
+ *
2235
+ * @callback EventTarget~EventListener
2236
+ * @this {EventTarget}
2237
+ *
2238
+ * @param {EventTarget~Event} event
2239
+ * the event that triggered this function
2240
+ *
2241
+ * @param {Object} [hash]
2242
+ * hash of data sent during the event
2243
+ */
2244
+
2245
+ /**
2246
+ * An object containing event names as keys and booleans as values.
2247
+ *
2248
+ * > NOTE: If an event name is set to a true value here {@link EventTarget#trigger}
2249
+ * will have extra functionality. See that function for more information.
2250
+ *
2251
+ * @property EventTarget.prototype.allowedEvents_
2252
+ * @private
2253
+ */
2254
+ EventTarget.prototype.allowedEvents_ = {};
2255
+
2256
+ /**
2257
+ * Adds an `event listener` to an instance of an `EventTarget`. An `event listener` is a
2258
+ * function that will get called when an event with a certain name gets triggered.
2259
+ *
2260
+ * @param {string|string[]} type
2261
+ * An event name or an array of event names.
2262
+ *
2263
+ * @param {EventTarget~EventListener} fn
2264
+ * The function to call with `EventTarget`s
2265
+ */
2266
+ EventTarget.prototype.on = function (type, fn) {
2267
+ // Remove the addEventListener alias before calling Events.on
2268
+ // so we don't get into an infinite type loop
2269
+ var ael = this.addEventListener;
2270
+
2271
+ this.addEventListener = function () {};
2272
+ on(this, type, fn);
2273
+ this.addEventListener = ael;
2274
+ };
2275
+
2276
+ /**
2277
+ * An alias of {@link EventTarget#on}. Allows `EventTarget` to mimic
2278
+ * the standard DOM API.
2279
+ *
2280
+ * @function
2281
+ * @see {@link EventTarget#on}
2282
+ */
2283
+ EventTarget.prototype.addEventListener = EventTarget.prototype.on;
2284
+
2285
+ /**
2286
+ * Removes an `event listener` for a specific event from an instance of `EventTarget`.
2287
+ * This makes it so that the `event listener` will no longer get called when the
2288
+ * named event happens.
2289
+ *
2290
+ * @param {string|string[]} type
2291
+ * An event name or an array of event names.
2292
+ *
2293
+ * @param {EventTarget~EventListener} fn
2294
+ * The function to remove.
2295
+ */
2296
+ EventTarget.prototype.off = function (type, fn) {
2297
+ off(this, type, fn);
2298
+ };
2299
+
2300
+ /**
2301
+ * An alias of {@link EventTarget#off}. Allows `EventTarget` to mimic
2302
+ * the standard DOM API.
2303
+ *
2304
+ * @function
2305
+ * @see {@link EventTarget#off}
2306
+ */
2307
+ EventTarget.prototype.removeEventListener = EventTarget.prototype.off;
2308
+
2309
+ /**
2310
+ * This function will add an `event listener` that gets triggered only once. After the
2311
+ * first trigger it will get removed. This is like adding an `event listener`
2312
+ * with {@link EventTarget#on} that calls {@link EventTarget#off} on itself.
2313
+ *
2314
+ * @param {string|string[]} type
2315
+ * An event name or an array of event names.
2316
+ *
2317
+ * @param {EventTarget~EventListener} fn
2318
+ * The function to be called once for each event name.
2319
+ */
2320
+ EventTarget.prototype.one = function (type, fn) {
2321
+ // Remove the addEventListener alialing Events.on
2322
+ // so we don't get into an infinite type loop
2323
+ var ael = this.addEventListener;
2324
+
2325
+ this.addEventListener = function () {};
2326
+ one(this, type, fn);
2327
+ this.addEventListener = ael;
2328
+ };
2329
+
2330
+ /**
2331
+ * This function causes an event to happen. This will then cause any `event listeners`
2332
+ * that are waiting for that event, to get called. If there are no `event listeners`
2333
+ * for an event then nothing will happen.
2334
+ *
2335
+ * If the name of the `Event` that is being triggered is in `EventTarget.allowedEvents_`.
2336
+ * Trigger will also call the `on` + `uppercaseEventName` function.
2337
+ *
2338
+ * Example:
2339
+ * 'click' is in `EventTarget.allowedEvents_`, so, trigger will attempt to call
2340
+ * `onClick` if it exists.
2341
+ *
2342
+ * @param {string|EventTarget~Event|Object} event
2343
+ * The name of the event, an `Event`, or an object with a key of type set to
2344
+ * an event name.
2345
+ */
2346
+ EventTarget.prototype.trigger = function (event) {
2347
+ var type = event.type || event;
2348
+
2349
+ if (typeof event === 'string') {
2350
+ event = { type: type };
2351
+ }
2352
+ event = fixEvent(event);
2353
+
2354
+ if (this.allowedEvents_[type] && this['on' + type]) {
2355
+ this['on' + type](event);
2356
+ }
2357
+
2358
+ trigger(this, event);
2359
+ };
2360
+
2361
+ /**
2362
+ * An alias of {@link EventTarget#trigger}. Allows `EventTarget` to mimic
2363
+ * the standard DOM API.
2364
+ *
2365
+ * @function
2366
+ * @see {@link EventTarget#trigger}
2367
+ */
2368
+ EventTarget.prototype.dispatchEvent = EventTarget.prototype.trigger;
2369
+
2370
+ /**
2371
+ * @file mixins/evented.js
2372
+ * @module evented
2373
+ */
2374
+
2375
+ /**
2376
+ * Returns whether or not an object has had the evented mixin applied.
2377
+ *
2378
+ * @param {Object} object
2379
+ * An object to test.
2380
+ *
2381
+ * @return {boolean}
2382
+ * Whether or not the object appears to be evented.
2383
+ */
2384
+ var isEvented = function isEvented(object) {
2385
+ return object instanceof EventTarget || !!object.eventBusEl_ && ['on', 'one', 'off', 'trigger'].every(function (k) {
2386
+ return typeof object[k] === 'function';
2387
+ });
2388
+ };
2389
+
2390
+ /**
2391
+ * Whether a value is a valid event type - non-empty string or array.
2392
+ *
2393
+ * @private
2394
+ * @param {string|Array} type
2395
+ * The type value to test.
2396
+ *
2397
+ * @return {boolean}
2398
+ * Whether or not the type is a valid event type.
2399
+ */
2400
+ var isValidEventType = function isValidEventType(type) {
2401
+ return (
2402
+ // The regex here verifies that the `type` contains at least one non-
2403
+ // whitespace character.
2404
+ typeof type === 'string' && /\S/.test(type) || Array.isArray(type) && !!type.length
2405
+ );
2406
+ };
2407
+
2408
+ /**
2409
+ * Validates a value to determine if it is a valid event target. Throws if not.
2410
+ *
2411
+ * @private
2412
+ * @throws {Error}
2413
+ * If the target does not appear to be a valid event target.
2414
+ *
2415
+ * @param {Object} target
2416
+ * The object to test.
2417
+ */
2418
+ var validateTarget = function validateTarget(target) {
2419
+ if (!target.nodeName && !isEvented(target)) {
2420
+ throw new Error('Invalid target; must be a DOM node or evented object.');
2421
+ }
2422
+ };
2423
+
2424
+ /**
2425
+ * Validates a value to determine if it is a valid event target. Throws if not.
2426
+ *
2427
+ * @private
2428
+ * @throws {Error}
2429
+ * If the type does not appear to be a valid event type.
2430
+ *
2431
+ * @param {string|Array} type
2432
+ * The type to test.
2433
+ */
2434
+ var validateEventType = function validateEventType(type) {
2435
+ if (!isValidEventType(type)) {
2436
+ throw new Error('Invalid event type; must be a non-empty string or array.');
2437
+ }
2438
+ };
2439
+
2440
+ /**
2441
+ * Validates a value to determine if it is a valid listener. Throws if not.
2442
+ *
2443
+ * @private
2444
+ * @throws {Error}
2445
+ * If the listener is not a function.
2446
+ *
2447
+ * @param {Function} listener
2448
+ * The listener to test.
2449
+ */
2450
+ var validateListener = function validateListener(listener) {
2451
+ if (typeof listener !== 'function') {
2452
+ throw new Error('Invalid listener; must be a function.');
2453
+ }
2454
+ };
2455
+
2456
+ /**
2457
+ * Takes an array of arguments given to `on()` or `one()`, validates them, and
2458
+ * normalizes them into an object.
2459
+ *
2460
+ * @private
2461
+ * @param {Object} self
2462
+ * The evented object on which `on()` or `one()` was called. This
2463
+ * object will be bound as the `this` value for the listener.
2464
+ *
2465
+ * @param {Array} args
2466
+ * An array of arguments passed to `on()` or `one()`.
2467
+ *
2468
+ * @return {Object}
2469
+ * An object containing useful values for `on()` or `one()` calls.
2470
+ */
2471
+ var normalizeListenArgs = function normalizeListenArgs(self, args) {
2472
+
2473
+ // If the number of arguments is less than 3, the target is always the
2474
+ // evented object itself.
2475
+ var isTargetingSelf = args.length < 3 || args[0] === self || args[0] === self.eventBusEl_;
2476
+ var target = void 0;
2477
+ var type = void 0;
2478
+ var listener = void 0;
2479
+
2480
+ if (isTargetingSelf) {
2481
+ target = self.eventBusEl_;
2482
+
2483
+ // Deal with cases where we got 3 arguments, but we are still listening to
2484
+ // the evented object itself.
2485
+ if (args.length >= 3) {
2486
+ args.shift();
2487
+ }
2488
+
2489
+ type = args[0];
2490
+ listener = args[1];
2491
+ } else {
2492
+ target = args[0];
2493
+ type = args[1];
2494
+ listener = args[2];
2495
+ }
2496
+
2497
+ validateTarget(target);
2498
+ validateEventType(type);
2499
+ validateListener(listener);
2500
+
2501
+ listener = bind(self, listener);
2502
+
2503
+ return { isTargetingSelf: isTargetingSelf, target: target, type: type, listener: listener };
2504
+ };
2505
+
2506
+ /**
2507
+ * Adds the listener to the event type(s) on the target, normalizing for
2508
+ * the type of target.
2509
+ *
2510
+ * @private
2511
+ * @param {Element|Object} target
2512
+ * A DOM node or evented object.
2513
+ *
2514
+ * @param {string} method
2515
+ * The event binding method to use ("on" or "one").
2516
+ *
2517
+ * @param {string|Array} type
2518
+ * One or more event type(s).
2519
+ *
2520
+ * @param {Function} listener
2521
+ * A listener function.
2522
+ */
2523
+ var listen = function listen(target, method, type, listener) {
2524
+ validateTarget(target);
2525
+
2526
+ if (target.nodeName) {
2527
+ Events[method](target, type, listener);
2528
+ } else {
2529
+ target[method](type, listener);
2530
+ }
2531
+ };
2532
+
2533
+ /**
2534
+ * Contains methods that provide event capabilities to an object which is passed
2535
+ * to {@link module:evented|evented}.
2536
+ *
2537
+ * @mixin EventedMixin
2538
+ */
2539
+ var EventedMixin = {
2540
+
2541
+ /**
2542
+ * Add a listener to an event (or events) on this object or another evented
2543
+ * object.
2544
+ *
2545
+ * @param {string|Array|Element|Object} targetOrType
2546
+ * If this is a string or array, it represents the event type(s)
2547
+ * that will trigger the listener.
2548
+ *
2549
+ * Another evented object can be passed here instead, which will
2550
+ * cause the listener to listen for events on _that_ object.
2551
+ *
2552
+ * In either case, the listener's `this` value will be bound to
2553
+ * this object.
2554
+ *
2555
+ * @param {string|Array|Function} typeOrListener
2556
+ * If the first argument was a string or array, this should be the
2557
+ * listener function. Otherwise, this is a string or array of event
2558
+ * type(s).
2559
+ *
2560
+ * @param {Function} [listener]
2561
+ * If the first argument was another evented object, this will be
2562
+ * the listener function.
2563
+ */
2564
+ on: function on$$1() {
2565
+ var _this = this;
2566
+
2567
+ for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
2568
+ args[_key] = arguments[_key];
2569
+ }
2570
+
2571
+ var _normalizeListenArgs = normalizeListenArgs(this, args),
2572
+ isTargetingSelf = _normalizeListenArgs.isTargetingSelf,
2573
+ target = _normalizeListenArgs.target,
2574
+ type = _normalizeListenArgs.type,
2575
+ listener = _normalizeListenArgs.listener;
2576
+
2577
+ listen(target, 'on', type, listener);
2578
+
2579
+ // If this object is listening to another evented object.
2580
+ if (!isTargetingSelf) {
2581
+
2582
+ // If this object is disposed, remove the listener.
2583
+ var removeListenerOnDispose = function removeListenerOnDispose() {
2584
+ return _this.off(target, type, listener);
2585
+ };
2586
+
2587
+ // Use the same function ID as the listener so we can remove it later it
2588
+ // using the ID of the original listener.
2589
+ removeListenerOnDispose.guid = listener.guid;
2590
+
2591
+ // Add a listener to the target's dispose event as well. This ensures
2592
+ // that if the target is disposed BEFORE this object, we remove the
2593
+ // removal listener that was just added. Otherwise, we create a memory leak.
2594
+ var removeRemoverOnTargetDispose = function removeRemoverOnTargetDispose() {
2595
+ return _this.off('dispose', removeListenerOnDispose);
2596
+ };
2597
+
2598
+ // Use the same function ID as the listener so we can remove it later
2599
+ // it using the ID of the original listener.
2600
+ removeRemoverOnTargetDispose.guid = listener.guid;
2601
+
2602
+ listen(this, 'on', 'dispose', removeListenerOnDispose);
2603
+ listen(target, 'on', 'dispose', removeRemoverOnTargetDispose);
2604
+ }
2605
+ },
2606
+
2607
+
2608
+ /**
2609
+ * Add a listener to an event (or events) on this object or another evented
2610
+ * object. The listener will only be called once and then removed.
2611
+ *
2612
+ * @param {string|Array|Element|Object} targetOrType
2613
+ * If this is a string or array, it represents the event type(s)
2614
+ * that will trigger the listener.
2615
+ *
2616
+ * Another evented object can be passed here instead, which will
2617
+ * cause the listener to listen for events on _that_ object.
2618
+ *
2619
+ * In either case, the listener's `this` value will be bound to
2620
+ * this object.
2621
+ *
2622
+ * @param {string|Array|Function} typeOrListener
2623
+ * If the first argument was a string or array, this should be the
2624
+ * listener function. Otherwise, this is a string or array of event
2625
+ * type(s).
2626
+ *
2627
+ * @param {Function} [listener]
2628
+ * If the first argument was another evented object, this will be
2629
+ * the listener function.
2630
+ */
2631
+ one: function one$$1() {
2632
+ var _this2 = this;
2633
+
2634
+ for (var _len2 = arguments.length, args = Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
2635
+ args[_key2] = arguments[_key2];
2636
+ }
2637
+
2638
+ var _normalizeListenArgs2 = normalizeListenArgs(this, args),
2639
+ isTargetingSelf = _normalizeListenArgs2.isTargetingSelf,
2640
+ target = _normalizeListenArgs2.target,
2641
+ type = _normalizeListenArgs2.type,
2642
+ listener = _normalizeListenArgs2.listener;
2643
+
2644
+ // Targeting this evented object.
2645
+
2646
+
2647
+ if (isTargetingSelf) {
2648
+ listen(target, 'one', type, listener);
2649
+
2650
+ // Targeting another evented object.
2651
+ } else {
2652
+ var wrapper = function wrapper() {
2653
+ for (var _len3 = arguments.length, largs = Array(_len3), _key3 = 0; _key3 < _len3; _key3++) {
2654
+ largs[_key3] = arguments[_key3];
2655
+ }
2656
+
2657
+ _this2.off(target, type, wrapper);
2658
+ listener.apply(null, largs);
2659
+ };
2660
+
2661
+ // Use the same function ID as the listener so we can remove it later
2662
+ // it using the ID of the original listener.
2663
+ wrapper.guid = listener.guid;
2664
+ listen(target, 'one', type, wrapper);
2665
+ }
2666
+ },
2667
+
2668
+
2669
+ /**
2670
+ * Removes listener(s) from event(s) on an evented object.
2671
+ *
2672
+ * @param {string|Array|Element|Object} [targetOrType]
2673
+ * If this is a string or array, it represents the event type(s).
2674
+ *
2675
+ * Another evented object can be passed here instead, in which case
2676
+ * ALL 3 arguments are _required_.
2677
+ *
2678
+ * @param {string|Array|Function} [typeOrListener]
2679
+ * If the first argument was a string or array, this may be the
2680
+ * listener function. Otherwise, this is a string or array of event
2681
+ * type(s).
2682
+ *
2683
+ * @param {Function} [listener]
2684
+ * If the first argument was another evented object, this will be
2685
+ * the listener function; otherwise, _all_ listeners bound to the
2686
+ * event type(s) will be removed.
2687
+ */
2688
+ off: function off$$1(targetOrType, typeOrListener, listener) {
2689
+
2690
+ // Targeting this evented object.
2691
+ if (!targetOrType || isValidEventType(targetOrType)) {
2692
+ off(this.eventBusEl_, targetOrType, typeOrListener);
2693
+
2694
+ // Targeting another evented object.
2695
+ } else {
2696
+ var target = targetOrType;
2697
+ var type = typeOrListener;
2698
+
2699
+ // Fail fast and in a meaningful way!
2700
+ validateTarget(target);
2701
+ validateEventType(type);
2702
+ validateListener(listener);
2703
+
2704
+ // Ensure there's at least a guid, even if the function hasn't been used
2705
+ listener = bind(this, listener);
2706
+
2707
+ // Remove the dispose listener on this evented object, which was given
2708
+ // the same guid as the event listener in on().
2709
+ this.off('dispose', listener);
2710
+
2711
+ if (target.nodeName) {
2712
+ off(target, type, listener);
2713
+ off(target, 'dispose', listener);
2714
+ } else if (isEvented(target)) {
2715
+ target.off(type, listener);
2716
+ target.off('dispose', listener);
2717
+ }
2718
+ }
2719
+ },
2720
+
2721
+
2722
+ /**
2723
+ * Fire an event on this evented object, causing its listeners to be called.
2724
+ *
2725
+ * @param {string|Object} event
2726
+ * An event type or an object with a type property.
2727
+ *
2728
+ * @param {Object} [hash]
2729
+ * An additional object to pass along to listeners.
2730
+ *
2731
+ * @returns {boolean}
2732
+ * Whether or not the default behavior was prevented.
2733
+ */
2734
+ trigger: function trigger$$1(event, hash) {
2735
+ return trigger(this.eventBusEl_, event, hash);
2736
+ }
2737
+ };
2738
+
2739
+ /**
2740
+ * Applies {@link module:evented~EventedMixin|EventedMixin} to a target object.
2741
+ *
2742
+ * @param {Object} target
2743
+ * The object to which to add event methods.
2744
+ *
2745
+ * @param {Object} [options={}]
2746
+ * Options for customizing the mixin behavior.
2747
+ *
2748
+ * @param {String} [options.eventBusKey]
2749
+ * By default, adds a `eventBusEl_` DOM element to the target object,
2750
+ * which is used as an event bus. If the target object already has a
2751
+ * DOM element that should be used, pass its key here.
2752
+ *
2753
+ * @return {Object}
2754
+ * The target object.
2755
+ */
2756
+ function evented(target) {
2757
+ var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
2758
+ var eventBusKey = options.eventBusKey;
2759
+
2760
+ // Set or create the eventBusEl_.
2761
+
2762
+ if (eventBusKey) {
2763
+ if (!target[eventBusKey].nodeName) {
2764
+ throw new Error('The eventBusKey "' + eventBusKey + '" does not refer to an element.');
2765
+ }
2766
+ target.eventBusEl_ = target[eventBusKey];
2767
+ } else {
2768
+ target.eventBusEl_ = createEl('span', { className: 'vjs-event-bus' });
2769
+ }
2770
+
2771
+ assign(target, EventedMixin);
2772
+
2773
+ // When any evented object is disposed, it removes all its listeners.
2774
+ target.on('dispose', function () {
2775
+ target.off();
2776
+ window_1.setTimeout(function () {
2777
+ target.eventBusEl_ = null;
2778
+ }, 0);
2779
+ });
2780
+
2781
+ return target;
2782
+ }
2783
+
2784
+ /**
2785
+ * @file mixins/stateful.js
2786
+ * @module stateful
2787
+ */
2788
+
2789
+ /**
2790
+ * Contains methods that provide statefulness to an object which is passed
2791
+ * to {@link module:stateful}.
2792
+ *
2793
+ * @mixin StatefulMixin
2794
+ */
2795
+ var StatefulMixin = {
2796
+
2797
+ /**
2798
+ * A hash containing arbitrary keys and values representing the state of
2799
+ * the object.
2800
+ *
2801
+ * @type {Object}
2802
+ */
2803
+ state: {},
2804
+
2805
+ /**
2806
+ * Set the state of an object by mutating its
2807
+ * {@link module:stateful~StatefulMixin.state|state} object in place.
2808
+ *
2809
+ * @fires module:stateful~StatefulMixin#statechanged
2810
+ * @param {Object|Function} stateUpdates
2811
+ * A new set of properties to shallow-merge into the plugin state.
2812
+ * Can be a plain object or a function returning a plain object.
2813
+ *
2814
+ * @returns {Object|undefined}
2815
+ * An object containing changes that occurred. If no changes
2816
+ * occurred, returns `undefined`.
2817
+ */
2818
+ setState: function setState(stateUpdates) {
2819
+ var _this = this;
2820
+
2821
+ // Support providing the `stateUpdates` state as a function.
2822
+ if (typeof stateUpdates === 'function') {
2823
+ stateUpdates = stateUpdates();
2824
+ }
2825
+
2826
+ var changes = void 0;
2827
+
2828
+ each(stateUpdates, function (value, key) {
2829
+
2830
+ // Record the change if the value is different from what's in the
2831
+ // current state.
2832
+ if (_this.state[key] !== value) {
2833
+ changes = changes || {};
2834
+ changes[key] = {
2835
+ from: _this.state[key],
2836
+ to: value
2837
+ };
2838
+ }
2839
+
2840
+ _this.state[key] = value;
2841
+ });
2842
+
2843
+ // Only trigger "statechange" if there were changes AND we have a trigger
2844
+ // function. This allows us to not require that the target object be an
2845
+ // evented object.
2846
+ if (changes && isEvented(this)) {
2847
+
2848
+ /**
2849
+ * An event triggered on an object that is both
2850
+ * {@link module:stateful|stateful} and {@link module:evented|evented}
2851
+ * indicating that its state has changed.
2852
+ *
2853
+ * @event module:stateful~StatefulMixin#statechanged
2854
+ * @type {Object}
2855
+ * @property {Object} changes
2856
+ * A hash containing the properties that were changed and
2857
+ * the values they were changed `from` and `to`.
2858
+ */
2859
+ this.trigger({
2860
+ changes: changes,
2861
+ type: 'statechanged'
2862
+ });
2863
+ }
2864
+
2865
+ return changes;
2866
+ }
2867
+ };
2868
+
2869
+ /**
2870
+ * Applies {@link module:stateful~StatefulMixin|StatefulMixin} to a target
2871
+ * object.
2872
+ *
2873
+ * If the target object is {@link module:evented|evented} and has a
2874
+ * `handleStateChanged` method, that method will be automatically bound to the
2875
+ * `statechanged` event on itself.
2876
+ *
2877
+ * @param {Object} target
2878
+ * The object to be made stateful.
2879
+ *
2880
+ * @param {Object} [defaultState]
2881
+ * A default set of properties to populate the newly-stateful object's
2882
+ * `state` property.
2883
+ *
2884
+ * @returns {Object}
2885
+ * Returns the `target`.
2886
+ */
2887
+ function stateful(target, defaultState) {
2888
+ assign(target, StatefulMixin);
2889
+
2890
+ // This happens after the mixing-in because we need to replace the `state`
2891
+ // added in that step.
2892
+ target.state = assign({}, target.state, defaultState);
2893
+
2894
+ // Auto-bind the `handleStateChanged` method of the target object if it exists.
2895
+ if (typeof target.handleStateChanged === 'function' && isEvented(target)) {
2896
+ target.on('statechanged', target.handleStateChanged);
2897
+ }
2898
+
2899
+ return target;
2900
+ }
2901
+
2902
+ /**
2903
+ * @file to-title-case.js
2904
+ * @module to-title-case
2905
+ */
2906
+
2907
+ /**
2908
+ * Uppercase the first letter of a string.
2909
+ *
2910
+ * @param {string} string
2911
+ * String to be uppercased
2912
+ *
2913
+ * @return {string}
2914
+ * The string with an uppercased first letter
2915
+ */
2916
+ function toTitleCase(string) {
2917
+ if (typeof string !== 'string') {
2918
+ return string;
2919
+ }
2920
+
2921
+ return string.charAt(0).toUpperCase() + string.slice(1);
2922
+ }
2923
+
2924
+ /**
2925
+ * Compares the TitleCase versions of the two strings for equality.
2926
+ *
2927
+ * @param {string} str1
2928
+ * The first string to compare
2929
+ *
2930
+ * @param {string} str2
2931
+ * The second string to compare
2932
+ *
2933
+ * @return {boolean}
2934
+ * Whether the TitleCase versions of the strings are equal
2935
+ */
2936
+ function titleCaseEquals(str1, str2) {
2937
+ return toTitleCase(str1) === toTitleCase(str2);
2938
+ }
2939
+
2940
+ /**
2941
+ * @file merge-options.js
2942
+ * @module merge-options
2943
+ */
2944
+
2945
+ /**
2946
+ * Deep-merge one or more options objects, recursively merging **only** plain
2947
+ * object properties.
2948
+ *
2949
+ * @param {Object[]} sources
2950
+ * One or more objects to merge into a new object.
2951
+ *
2952
+ * @returns {Object}
2953
+ * A new object that is the merged result of all sources.
2954
+ */
2955
+ function mergeOptions() {
2956
+ var result = {};
2957
+
2958
+ for (var _len = arguments.length, sources = Array(_len), _key = 0; _key < _len; _key++) {
2959
+ sources[_key] = arguments[_key];
2960
+ }
2961
+
2962
+ sources.forEach(function (source) {
2963
+ if (!source) {
2964
+ return;
2965
+ }
2966
+
2967
+ each(source, function (value, key) {
2968
+ if (!isPlain(value)) {
2969
+ result[key] = value;
2970
+ return;
2971
+ }
2972
+
2973
+ if (!isPlain(result[key])) {
2974
+ result[key] = {};
2975
+ }
2976
+
2977
+ result[key] = mergeOptions(result[key], value);
2978
+ });
2979
+ });
2980
+
2981
+ return result;
2982
+ }
2983
+
2984
+ /**
2985
+ * Player Component - Base class for all UI objects
2986
+ *
2987
+ * @file component.js
2988
+ */
2989
+
2990
+ /**
2991
+ * Base class for all UI Components.
2992
+ * Components are UI objects which represent both a javascript object and an element
2993
+ * in the DOM. They can be children of other components, and can have
2994
+ * children themselves.
2995
+ *
2996
+ * Components can also use methods from {@link EventTarget}
2997
+ */
2998
+
2999
+ var Component = function () {
3000
+
3001
+ /**
3002
+ * A callback that is called when a component is ready. Does not have any
3003
+ * paramters and any callback value will be ignored.
3004
+ *
3005
+ * @callback Component~ReadyCallback
3006
+ * @this Component
3007
+ */
3008
+
3009
+ /**
3010
+ * Creates an instance of this class.
3011
+ *
3012
+ * @param {Player} player
3013
+ * The `Player` that this class should be attached to.
3014
+ *
3015
+ * @param {Object} [options]
3016
+ * The key/value store of player options.
3017
+ *
3018
+ * @param {Object[]} [options.children]
3019
+ * An array of children objects to intialize this component with. Children objects have
3020
+ * a name property that will be used if more than one component of the same type needs to be
3021
+ * added.
3022
+ *
3023
+ * @param {Component~ReadyCallback} [ready]
3024
+ * Function that gets called when the `Component` is ready.
3025
+ */
3026
+ function Component(player, options, ready) {
3027
+ classCallCheck(this, Component);
3028
+
3029
+
3030
+ // The component might be the player itself and we can't pass `this` to super
3031
+ if (!player && this.play) {
3032
+ this.player_ = player = this; // eslint-disable-line
3033
+ } else {
3034
+ this.player_ = player;
3035
+ }
3036
+
3037
+ // Make a copy of prototype.options_ to protect against overriding defaults
3038
+ this.options_ = mergeOptions({}, this.options_);
3039
+
3040
+ // Updated options with supplied options
3041
+ options = this.options_ = mergeOptions(this.options_, options);
3042
+
3043
+ // Get ID from options or options element if one is supplied
3044
+ this.id_ = options.id || options.el && options.el.id;
3045
+
3046
+ // If there was no ID from the options, generate one
3047
+ if (!this.id_) {
3048
+ // Don't require the player ID function in the case of mock players
3049
+ var id = player && player.id && player.id() || 'no_player';
3050
+
3051
+ this.id_ = id + '_component_' + newGUID();
3052
+ }
3053
+
3054
+ this.name_ = options.name || null;
3055
+
3056
+ // Create element if one wasn't provided in options
3057
+ if (options.el) {
3058
+ this.el_ = options.el;
3059
+ } else if (options.createEl !== false) {
3060
+ this.el_ = this.createEl();
3061
+ }
3062
+
3063
+ // if evented is anything except false, we want to mixin in evented
3064
+ if (options.evented !== false) {
3065
+ // Make this an evented object and use `el_`, if available, as its event bus
3066
+ evented(this, { eventBusKey: this.el_ ? 'el_' : null });
3067
+ }
3068
+ stateful(this, this.constructor.defaultState);
3069
+
3070
+ this.children_ = [];
3071
+ this.childIndex_ = {};
3072
+ this.childNameIndex_ = {};
3073
+
3074
+ // Add any child components in options
3075
+ if (options.initChildren !== false) {
3076
+ this.initChildren();
3077
+ }
3078
+
3079
+ this.ready(ready);
3080
+ // Don't want to trigger ready here or it will before init is actually
3081
+ // finished for all children that run this constructor
3082
+
3083
+ if (options.reportTouchActivity !== false) {
3084
+ this.enableTouchActivity();
3085
+ }
3086
+ }
3087
+
3088
+ /**
3089
+ * Dispose of the `Component` and all child components.
3090
+ *
3091
+ * @fires Component#dispose
3092
+ */
3093
+
3094
+
3095
+ Component.prototype.dispose = function dispose() {
3096
+
3097
+ /**
3098
+ * Triggered when a `Component` is disposed.
3099
+ *
3100
+ * @event Component#dispose
3101
+ * @type {EventTarget~Event}
3102
+ *
3103
+ * @property {boolean} [bubbles=false]
3104
+ * set to false so that the close event does not
3105
+ * bubble up
3106
+ */
3107
+ this.trigger({ type: 'dispose', bubbles: false });
3108
+
3109
+ // Dispose all children.
3110
+ if (this.children_) {
3111
+ for (var i = this.children_.length - 1; i >= 0; i--) {
3112
+ if (this.children_[i].dispose) {
3113
+ this.children_[i].dispose();
3114
+ }
3115
+ }
3116
+ }
3117
+
3118
+ // Delete child references
3119
+ this.children_ = null;
3120
+ this.childIndex_ = null;
3121
+ this.childNameIndex_ = null;
3122
+
3123
+ if (this.el_) {
3124
+ // Remove element from DOM
3125
+ if (this.el_.parentNode) {
3126
+ this.el_.parentNode.removeChild(this.el_);
3127
+ }
3128
+
3129
+ removeData(this.el_);
3130
+ this.el_ = null;
3131
+ }
3132
+
3133
+ // remove reference to the player after disposing of the element
3134
+ this.player_ = null;
3135
+ };
3136
+
3137
+ /**
3138
+ * Return the {@link Player} that the `Component` has attached to.
3139
+ *
3140
+ * @return {Player}
3141
+ * The player that this `Component` has attached to.
3142
+ */
3143
+
3144
+
3145
+ Component.prototype.player = function player() {
3146
+ return this.player_;
3147
+ };
3148
+
3149
+ /**
3150
+ * Deep merge of options objects with new options.
3151
+ * > Note: When both `obj` and `options` contain properties whose values are objects.
3152
+ * The two properties get merged using {@link module:mergeOptions}
3153
+ *
3154
+ * @param {Object} obj
3155
+ * The object that contains new options.
3156
+ *
3157
+ * @return {Object}
3158
+ * A new object of `this.options_` and `obj` merged together.
3159
+ *
3160
+ * @deprecated since version 5
3161
+ */
3162
+
3163
+
3164
+ Component.prototype.options = function options(obj) {
3165
+ log$1.warn('this.options() has been deprecated and will be moved to the constructor in 6.0');
3166
+
3167
+ if (!obj) {
3168
+ return this.options_;
3169
+ }
3170
+
3171
+ this.options_ = mergeOptions(this.options_, obj);
3172
+ return this.options_;
3173
+ };
3174
+
3175
+ /**
3176
+ * Get the `Component`s DOM element
3177
+ *
3178
+ * @return {Element}
3179
+ * The DOM element for this `Component`.
3180
+ */
3181
+
3182
+
3183
+ Component.prototype.el = function el() {
3184
+ return this.el_;
3185
+ };
3186
+
3187
+ /**
3188
+ * Create the `Component`s DOM element.
3189
+ *
3190
+ * @param {string} [tagName]
3191
+ * Element's DOM node type. e.g. 'div'
3192
+ *
3193
+ * @param {Object} [properties]
3194
+ * An object of properties that should be set.
3195
+ *
3196
+ * @param {Object} [attributes]
3197
+ * An object of attributes that should be set.
3198
+ *
3199
+ * @return {Element}
3200
+ * The element that gets created.
3201
+ */
3202
+
3203
+
3204
+ Component.prototype.createEl = function createEl$$1(tagName, properties, attributes) {
3205
+ return createEl(tagName, properties, attributes);
3206
+ };
3207
+
3208
+ /**
3209
+ * Localize a string given the string in english.
3210
+ *
3211
+ * If tokens are provided, it'll try and run a simple token replacement on the provided string.
3212
+ * The tokens it looks for look like `{1}` with the index being 1-indexed into the tokens array.
3213
+ *
3214
+ * If a `defaultValue` is provided, it'll use that over `string`,
3215
+ * if a value isn't found in provided language files.
3216
+ * This is useful if you want to have a descriptive key for token replacement
3217
+ * but have a succinct localized string and not require `en.json` to be included.
3218
+ *
3219
+ * Currently, it is used for the progress bar timing.
3220
+ * ```js
3221
+ * {
3222
+ * "progress bar timing: currentTime={1} duration={2}": "{1} of {2}"
3223
+ * }
3224
+ * ```
3225
+ * It is then used like so:
3226
+ * ```js
3227
+ * this.localize('progress bar timing: currentTime={1} duration{2}',
3228
+ * [this.player_.currentTime(), this.player_.duration()],
3229
+ * '{1} of {2}');
3230
+ * ```
3231
+ *
3232
+ * Which outputs something like: `01:23 of 24:56`.
3233
+ *
3234
+ *
3235
+ * @param {string} string
3236
+ * The string to localize and the key to lookup in the language files.
3237
+ * @param {string[]} [tokens]
3238
+ * If the current item has token replacements, provide the tokens here.
3239
+ * @param {string} [defaultValue]
3240
+ * Defaults to `string`. Can be a default value to use for token replacement
3241
+ * if the lookup key is needed to be separate.
3242
+ *
3243
+ * @return {string}
3244
+ * The localized string or if no localization exists the english string.
3245
+ */
3246
+
3247
+
3248
+ Component.prototype.localize = function localize(string, tokens) {
3249
+ var defaultValue = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : string;
3250
+
3251
+ var code = this.player_.language && this.player_.language();
3252
+ var languages = this.player_.languages && this.player_.languages();
3253
+ var language = languages && languages[code];
3254
+ var primaryCode = code && code.split('-')[0];
3255
+ var primaryLang = languages && languages[primaryCode];
3256
+
3257
+ var localizedString = defaultValue;
3258
+
3259
+ if (language && language[string]) {
3260
+ localizedString = language[string];
3261
+ } else if (primaryLang && primaryLang[string]) {
3262
+ localizedString = primaryLang[string];
3263
+ }
3264
+
3265
+ if (tokens) {
3266
+ localizedString = localizedString.replace(/\{(\d+)\}/g, function (match, index) {
3267
+ var value = tokens[index - 1];
3268
+ var ret = value;
3269
+
3270
+ if (typeof value === 'undefined') {
3271
+ ret = match;
3272
+ }
3273
+
3274
+ return ret;
3275
+ });
3276
+ }
3277
+
3278
+ return localizedString;
3279
+ };
3280
+
3281
+ /**
3282
+ * Return the `Component`s DOM element. This is where children get inserted.
3283
+ * This will usually be the the same as the element returned in {@link Component#el}.
3284
+ *
3285
+ * @return {Element}
3286
+ * The content element for this `Component`.
3287
+ */
3288
+
3289
+
3290
+ Component.prototype.contentEl = function contentEl() {
3291
+ return this.contentEl_ || this.el_;
3292
+ };
3293
+
3294
+ /**
3295
+ * Get this `Component`s ID
3296
+ *
3297
+ * @return {string}
3298
+ * The id of this `Component`
3299
+ */
3300
+
3301
+
3302
+ Component.prototype.id = function id() {
3303
+ return this.id_;
3304
+ };
3305
+
3306
+ /**
3307
+ * Get the `Component`s name. The name gets used to reference the `Component`
3308
+ * and is set during registration.
3309
+ *
3310
+ * @return {string}
3311
+ * The name of this `Component`.
3312
+ */
3313
+
3314
+
3315
+ Component.prototype.name = function name() {
3316
+ return this.name_;
3317
+ };
3318
+
3319
+ /**
3320
+ * Get an array of all child components
3321
+ *
3322
+ * @return {Array}
3323
+ * The children
3324
+ */
3325
+
3326
+
3327
+ Component.prototype.children = function children() {
3328
+ return this.children_;
3329
+ };
3330
+
3331
+ /**
3332
+ * Returns the child `Component` with the given `id`.
3333
+ *
3334
+ * @param {string} id
3335
+ * The id of the child `Component` to get.
3336
+ *
3337
+ * @return {Component|undefined}
3338
+ * The child `Component` with the given `id` or undefined.
3339
+ */
3340
+
3341
+
3342
+ Component.prototype.getChildById = function getChildById(id) {
3343
+ return this.childIndex_[id];
3344
+ };
3345
+
3346
+ /**
3347
+ * Returns the child `Component` with the given `name`.
3348
+ *
3349
+ * @param {string} name
3350
+ * The name of the child `Component` to get.
3351
+ *
3352
+ * @return {Component|undefined}
3353
+ * The child `Component` with the given `name` or undefined.
3354
+ */
3355
+
3356
+
3357
+ Component.prototype.getChild = function getChild(name) {
3358
+ if (!name) {
3359
+ return;
3360
+ }
3361
+
3362
+ name = toTitleCase(name);
3363
+
3364
+ return this.childNameIndex_[name];
3365
+ };
3366
+
3367
+ /**
3368
+ * Add a child `Component` inside the current `Component`.
3369
+ *
3370
+ *
3371
+ * @param {string|Component} child
3372
+ * The name or instance of a child to add.
3373
+ *
3374
+ * @param {Object} [options={}]
3375
+ * The key/value store of options that will get passed to children of
3376
+ * the child.
3377
+ *
3378
+ * @param {number} [index=this.children_.length]
3379
+ * The index to attempt to add a child into.
3380
+ *
3381
+ * @return {Component}
3382
+ * The `Component` that gets added as a child. When using a string the
3383
+ * `Component` will get created by this process.
3384
+ */
3385
+
3386
+
3387
+ Component.prototype.addChild = function addChild(child) {
3388
+ var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
3389
+ var index = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : this.children_.length;
3390
+
3391
+ var component = void 0;
3392
+ var componentName = void 0;
3393
+
3394
+ // If child is a string, create component with options
3395
+ if (typeof child === 'string') {
3396
+ componentName = toTitleCase(child);
3397
+
3398
+ var componentClassName = options.componentClass || componentName;
3399
+
3400
+ // Set name through options
3401
+ options.name = componentName;
3402
+
3403
+ // Create a new object & element for this controls set
3404
+ // If there's no .player_, this is a player
3405
+ var ComponentClass = Component.getComponent(componentClassName);
3406
+
3407
+ if (!ComponentClass) {
3408
+ throw new Error('Component ' + componentClassName + ' does not exist');
3409
+ }
3410
+
3411
+ // data stored directly on the videojs object may be
3412
+ // misidentified as a component to retain
3413
+ // backwards-compatibility with 4.x. check to make sure the
3414
+ // component class can be instantiated.
3415
+ if (typeof ComponentClass !== 'function') {
3416
+ return null;
3417
+ }
3418
+
3419
+ component = new ComponentClass(this.player_ || this, options);
3420
+
3421
+ // child is a component instance
3422
+ } else {
3423
+ component = child;
3424
+ }
3425
+
3426
+ this.children_.splice(index, 0, component);
3427
+
3428
+ if (typeof component.id === 'function') {
3429
+ this.childIndex_[component.id()] = component;
3430
+ }
3431
+
3432
+ // If a name wasn't used to create the component, check if we can use the
3433
+ // name function of the component
3434
+ componentName = componentName || component.name && toTitleCase(component.name());
3435
+
3436
+ if (componentName) {
3437
+ this.childNameIndex_[componentName] = component;
3438
+ }
3439
+
3440
+ // Add the UI object's element to the container div (box)
3441
+ // Having an element is not required
3442
+ if (typeof component.el === 'function' && component.el()) {
3443
+ var childNodes = this.contentEl().children;
3444
+ var refNode = childNodes[index] || null;
3445
+
3446
+ this.contentEl().insertBefore(component.el(), refNode);
3447
+ }
3448
+
3449
+ // Return so it can stored on parent object if desired.
3450
+ return component;
3451
+ };
3452
+
3453
+ /**
3454
+ * Remove a child `Component` from this `Component`s list of children. Also removes
3455
+ * the child `Component`s element from this `Component`s element.
3456
+ *
3457
+ * @param {Component} component
3458
+ * The child `Component` to remove.
3459
+ */
3460
+
3461
+
3462
+ Component.prototype.removeChild = function removeChild(component) {
3463
+ if (typeof component === 'string') {
3464
+ component = this.getChild(component);
3465
+ }
3466
+
3467
+ if (!component || !this.children_) {
3468
+ return;
3469
+ }
3470
+
3471
+ var childFound = false;
3472
+
3473
+ for (var i = this.children_.length - 1; i >= 0; i--) {
3474
+ if (this.children_[i] === component) {
3475
+ childFound = true;
3476
+ this.children_.splice(i, 1);
3477
+ break;
3478
+ }
3479
+ }
3480
+
3481
+ if (!childFound) {
3482
+ return;
3483
+ }
3484
+
3485
+ this.childIndex_[component.id()] = null;
3486
+ this.childNameIndex_[component.name()] = null;
3487
+
3488
+ var compEl = component.el();
3489
+
3490
+ if (compEl && compEl.parentNode === this.contentEl()) {
3491
+ this.contentEl().removeChild(component.el());
3492
+ }
3493
+ };
3494
+
3495
+ /**
3496
+ * Add and initialize default child `Component`s based upon options.
3497
+ */
3498
+
3499
+
3500
+ Component.prototype.initChildren = function initChildren() {
3501
+ var _this = this;
3502
+
3503
+ var children = this.options_.children;
3504
+
3505
+ if (children) {
3506
+ // `this` is `parent`
3507
+ var parentOptions = this.options_;
3508
+
3509
+ var handleAdd = function handleAdd(child) {
3510
+ var name = child.name;
3511
+ var opts = child.opts;
3512
+
3513
+ // Allow options for children to be set at the parent options
3514
+ // e.g. videojs(id, { controlBar: false });
3515
+ // instead of videojs(id, { children: { controlBar: false });
3516
+ if (parentOptions[name] !== undefined) {
3517
+ opts = parentOptions[name];
3518
+ }
3519
+
3520
+ // Allow for disabling default components
3521
+ // e.g. options['children']['posterImage'] = false
3522
+ if (opts === false) {
3523
+ return;
3524
+ }
3525
+
3526
+ // Allow options to be passed as a simple boolean if no configuration
3527
+ // is necessary.
3528
+ if (opts === true) {
3529
+ opts = {};
3530
+ }
3531
+
3532
+ // We also want to pass the original player options
3533
+ // to each component as well so they don't need to
3534
+ // reach back into the player for options later.
3535
+ opts.playerOptions = _this.options_.playerOptions;
3536
+
3537
+ // Create and add the child component.
3538
+ // Add a direct reference to the child by name on the parent instance.
3539
+ // If two of the same component are used, different names should be supplied
3540
+ // for each
3541
+ var newChild = _this.addChild(name, opts);
3542
+
3543
+ if (newChild) {
3544
+ _this[name] = newChild;
3545
+ }
3546
+ };
3547
+
3548
+ // Allow for an array of children details to passed in the options
3549
+ var workingChildren = void 0;
3550
+ var Tech = Component.getComponent('Tech');
3551
+
3552
+ if (Array.isArray(children)) {
3553
+ workingChildren = children;
3554
+ } else {
3555
+ workingChildren = Object.keys(children);
3556
+ }
3557
+
3558
+ workingChildren
3559
+ // children that are in this.options_ but also in workingChildren would
3560
+ // give us extra children we do not want. So, we want to filter them out.
3561
+ .concat(Object.keys(this.options_).filter(function (child) {
3562
+ return !workingChildren.some(function (wchild) {
3563
+ if (typeof wchild === 'string') {
3564
+ return child === wchild;
3565
+ }
3566
+ return child === wchild.name;
3567
+ });
3568
+ })).map(function (child) {
3569
+ var name = void 0;
3570
+ var opts = void 0;
3571
+
3572
+ if (typeof child === 'string') {
3573
+ name = child;
3574
+ opts = children[name] || _this.options_[name] || {};
3575
+ } else {
3576
+ name = child.name;
3577
+ opts = child;
3578
+ }
3579
+
3580
+ return { name: name, opts: opts };
3581
+ }).filter(function (child) {
3582
+ // we have to make sure that child.name isn't in the techOrder since
3583
+ // techs are registerd as Components but can't aren't compatible
3584
+ // See https://github.com/videojs/video.js/issues/2772
3585
+ var c = Component.getComponent(child.opts.componentClass || toTitleCase(child.name));
3586
+
3587
+ return c && !Tech.isTech(c);
3588
+ }).forEach(handleAdd);
3589
+ }
3590
+ };
3591
+
3592
+ /**
3593
+ * Builds the default DOM class name. Should be overriden by sub-components.
3594
+ *
3595
+ * @return {string}
3596
+ * The DOM class name for this object.
3597
+ *
3598
+ * @abstract
3599
+ */
3600
+
3601
+
3602
+ Component.prototype.buildCSSClass = function buildCSSClass() {
3603
+ // Child classes can include a function that does:
3604
+ // return 'CLASS NAME' + this._super();
3605
+ return '';
3606
+ };
3607
+
3608
+ /**
3609
+ * Bind a listener to the component's ready state.
3610
+ * Different from event listeners in that if the ready event has already happened
3611
+ * it will trigger the function immediately.
3612
+ *
3613
+ * @return {Component}
3614
+ * Returns itself; method can be chained.
3615
+ */
3616
+
3617
+
3618
+ Component.prototype.ready = function ready(fn) {
3619
+ var sync = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
3620
+
3621
+ if (!fn) {
3622
+ return;
3623
+ }
3624
+
3625
+ if (!this.isReady_) {
3626
+ this.readyQueue_ = this.readyQueue_ || [];
3627
+ this.readyQueue_.push(fn);
3628
+ return;
3629
+ }
3630
+
3631
+ if (sync) {
3632
+ fn.call(this);
3633
+ } else {
3634
+ // Call the function asynchronously by default for consistency
3635
+ this.setTimeout(fn, 1);
3636
+ }
3637
+ };
3638
+
3639
+ /**
3640
+ * Trigger all the ready listeners for this `Component`.
3641
+ *
3642
+ * @fires Component#ready
3643
+ */
3644
+
3645
+
3646
+ Component.prototype.triggerReady = function triggerReady() {
3647
+ this.isReady_ = true;
3648
+
3649
+ // Ensure ready is triggered asynchronously
3650
+ this.setTimeout(function () {
3651
+ var readyQueue = this.readyQueue_;
3652
+
3653
+ // Reset Ready Queue
3654
+ this.readyQueue_ = [];
3655
+
3656
+ if (readyQueue && readyQueue.length > 0) {
3657
+ readyQueue.forEach(function (fn) {
3658
+ fn.call(this);
3659
+ }, this);
3660
+ }
3661
+
3662
+ // Allow for using event listeners also
3663
+ /**
3664
+ * Triggered when a `Component` is ready.
3665
+ *
3666
+ * @event Component#ready
3667
+ * @type {EventTarget~Event}
3668
+ */
3669
+ this.trigger('ready');
3670
+ }, 1);
3671
+ };
3672
+
3673
+ /**
3674
+ * Find a single DOM element matching a `selector`. This can be within the `Component`s
3675
+ * `contentEl()` or another custom context.
3676
+ *
3677
+ * @param {string} selector
3678
+ * A valid CSS selector, which will be passed to `querySelector`.
3679
+ *
3680
+ * @param {Element|string} [context=this.contentEl()]
3681
+ * A DOM element within which to query. Can also be a selector string in
3682
+ * which case the first matching element will get used as context. If
3683
+ * missing `this.contentEl()` gets used. If `this.contentEl()` returns
3684
+ * nothing it falls back to `document`.
3685
+ *
3686
+ * @return {Element|null}
3687
+ * the dom element that was found, or null
3688
+ *
3689
+ * @see [Information on CSS Selectors](https://developer.mozilla.org/en-US/docs/Web/Guide/CSS/Getting_Started/Selectors)
3690
+ */
3691
+
3692
+
3693
+ Component.prototype.$ = function $$$1(selector, context) {
3694
+ return $(selector, context || this.contentEl());
3695
+ };
3696
+
3697
+ /**
3698
+ * Finds all DOM element matching a `selector`. This can be within the `Component`s
3699
+ * `contentEl()` or another custom context.
3700
+ *
3701
+ * @param {string} selector
3702
+ * A valid CSS selector, which will be passed to `querySelectorAll`.
3703
+ *
3704
+ * @param {Element|string} [context=this.contentEl()]
3705
+ * A DOM element within which to query. Can also be a selector string in
3706
+ * which case the first matching element will get used as context. If
3707
+ * missing `this.contentEl()` gets used. If `this.contentEl()` returns
3708
+ * nothing it falls back to `document`.
3709
+ *
3710
+ * @return {NodeList}
3711
+ * a list of dom elements that were found
3712
+ *
3713
+ * @see [Information on CSS Selectors](https://developer.mozilla.org/en-US/docs/Web/Guide/CSS/Getting_Started/Selectors)
3714
+ */
3715
+
3716
+
3717
+ Component.prototype.$$ = function $$$$1(selector, context) {
3718
+ return $$(selector, context || this.contentEl());
3719
+ };
3720
+
3721
+ /**
3722
+ * Check if a component's element has a CSS class name.
3723
+ *
3724
+ * @param {string} classToCheck
3725
+ * CSS class name to check.
3726
+ *
3727
+ * @return {boolean}
3728
+ * - True if the `Component` has the class.
3729
+ * - False if the `Component` does not have the class`
3730
+ */
3731
+
3732
+
3733
+ Component.prototype.hasClass = function hasClass$$1(classToCheck) {
3734
+ return hasClass(this.el_, classToCheck);
3735
+ };
3736
+
3737
+ /**
3738
+ * Add a CSS class name to the `Component`s element.
3739
+ *
3740
+ * @param {string} classToAdd
3741
+ * CSS class name to add
3742
+ */
3743
+
3744
+
3745
+ Component.prototype.addClass = function addClass$$1(classToAdd) {
3746
+ addClass(this.el_, classToAdd);
3747
+ };
3748
+
3749
+ /**
3750
+ * Remove a CSS class name from the `Component`s element.
3751
+ *
3752
+ * @param {string} classToRemove
3753
+ * CSS class name to remove
3754
+ */
3755
+
3756
+
3757
+ Component.prototype.removeClass = function removeClass$$1(classToRemove) {
3758
+ removeClass(this.el_, classToRemove);
3759
+ };
3760
+
3761
+ /**
3762
+ * Add or remove a CSS class name from the component's element.
3763
+ * - `classToToggle` gets added when {@link Component#hasClass} would return false.
3764
+ * - `classToToggle` gets removed when {@link Component#hasClass} would return true.
3765
+ *
3766
+ * @param {string} classToToggle
3767
+ * The class to add or remove based on (@link Component#hasClass}
3768
+ *
3769
+ * @param {boolean|Dom~predicate} [predicate]
3770
+ * An {@link Dom~predicate} function or a boolean
3771
+ */
3772
+
3773
+
3774
+ Component.prototype.toggleClass = function toggleClass$$1(classToToggle, predicate) {
3775
+ toggleClass(this.el_, classToToggle, predicate);
3776
+ };
3777
+
3778
+ /**
3779
+ * Show the `Component`s element if it is hidden by removing the
3780
+ * 'vjs-hidden' class name from it.
3781
+ */
3782
+
3783
+
3784
+ Component.prototype.show = function show() {
3785
+ this.removeClass('vjs-hidden');
3786
+ };
3787
+
3788
+ /**
3789
+ * Hide the `Component`s element if it is currently showing by adding the
3790
+ * 'vjs-hidden` class name to it.
3791
+ */
3792
+
3793
+
3794
+ Component.prototype.hide = function hide() {
3795
+ this.addClass('vjs-hidden');
3796
+ };
3797
+
3798
+ /**
3799
+ * Lock a `Component`s element in its visible state by adding the 'vjs-lock-showing'
3800
+ * class name to it. Used during fadeIn/fadeOut.
3801
+ *
3802
+ * @private
3803
+ */
3804
+
3805
+
3806
+ Component.prototype.lockShowing = function lockShowing() {
3807
+ this.addClass('vjs-lock-showing');
3808
+ };
3809
+
3810
+ /**
3811
+ * Unlock a `Component`s element from its visible state by removing the 'vjs-lock-showing'
3812
+ * class name from it. Used during fadeIn/fadeOut.
3813
+ *
3814
+ * @private
3815
+ */
3816
+
3817
+
3818
+ Component.prototype.unlockShowing = function unlockShowing() {
3819
+ this.removeClass('vjs-lock-showing');
3820
+ };
3821
+
3822
+ /**
3823
+ * Get the value of an attribute on the `Component`s element.
3824
+ *
3825
+ * @param {string} attribute
3826
+ * Name of the attribute to get the value from.
3827
+ *
3828
+ * @return {string|null}
3829
+ * - The value of the attribute that was asked for.
3830
+ * - Can be an empty string on some browsers if the attribute does not exist
3831
+ * or has no value
3832
+ * - Most browsers will return null if the attibute does not exist or has
3833
+ * no value.
3834
+ *
3835
+ * @see [DOM API]{@link https://developer.mozilla.org/en-US/docs/Web/API/Element/getAttribute}
3836
+ */
3837
+
3838
+
3839
+ Component.prototype.getAttribute = function getAttribute$$1(attribute) {
3840
+ return getAttribute(this.el_, attribute);
3841
+ };
3842
+
3843
+ /**
3844
+ * Set the value of an attribute on the `Component`'s element
3845
+ *
3846
+ * @param {string} attribute
3847
+ * Name of the attribute to set.
3848
+ *
3849
+ * @param {string} value
3850
+ * Value to set the attribute to.
3851
+ *
3852
+ * @see [DOM API]{@link https://developer.mozilla.org/en-US/docs/Web/API/Element/setAttribute}
3853
+ */
3854
+
3855
+
3856
+ Component.prototype.setAttribute = function setAttribute$$1(attribute, value) {
3857
+ setAttribute(this.el_, attribute, value);
3858
+ };
3859
+
3860
+ /**
3861
+ * Remove an attribute from the `Component`s element.
3862
+ *
3863
+ * @param {string} attribute
3864
+ * Name of the attribute to remove.
3865
+ *
3866
+ * @see [DOM API]{@link https://developer.mozilla.org/en-US/docs/Web/API/Element/removeAttribute}
3867
+ */
3868
+
3869
+
3870
+ Component.prototype.removeAttribute = function removeAttribute$$1(attribute) {
3871
+ removeAttribute(this.el_, attribute);
3872
+ };
3873
+
3874
+ /**
3875
+ * Get or set the width of the component based upon the CSS styles.
3876
+ * See {@link Component#dimension} for more detailed information.
3877
+ *
3878
+ * @param {number|string} [num]
3879
+ * The width that you want to set postfixed with '%', 'px' or nothing.
3880
+ *
3881
+ * @param {boolean} [skipListeners]
3882
+ * Skip the componentresize event trigger
3883
+ *
3884
+ * @return {number|string}
3885
+ * The width when getting, zero if there is no width. Can be a string
3886
+ * postpixed with '%' or 'px'.
3887
+ */
3888
+
3889
+
3890
+ Component.prototype.width = function width(num, skipListeners) {
3891
+ return this.dimension('width', num, skipListeners);
3892
+ };
3893
+
3894
+ /**
3895
+ * Get or set the height of the component based upon the CSS styles.
3896
+ * See {@link Component#dimension} for more detailed information.
3897
+ *
3898
+ * @param {number|string} [num]
3899
+ * The height that you want to set postfixed with '%', 'px' or nothing.
3900
+ *
3901
+ * @param {boolean} [skipListeners]
3902
+ * Skip the componentresize event trigger
3903
+ *
3904
+ * @return {number|string}
3905
+ * The width when getting, zero if there is no width. Can be a string
3906
+ * postpixed with '%' or 'px'.
3907
+ */
3908
+
3909
+
3910
+ Component.prototype.height = function height(num, skipListeners) {
3911
+ return this.dimension('height', num, skipListeners);
3912
+ };
3913
+
3914
+ /**
3915
+ * Set both the width and height of the `Component` element at the same time.
3916
+ *
3917
+ * @param {number|string} width
3918
+ * Width to set the `Component`s element to.
3919
+ *
3920
+ * @param {number|string} height
3921
+ * Height to set the `Component`s element to.
3922
+ */
3923
+
3924
+
3925
+ Component.prototype.dimensions = function dimensions(width, height) {
3926
+ // Skip componentresize listeners on width for optimization
3927
+ this.width(width, true);
3928
+ this.height(height);
3929
+ };
3930
+
3931
+ /**
3932
+ * Get or set width or height of the `Component` element. This is the shared code
3933
+ * for the {@link Component#width} and {@link Component#height}.
3934
+ *
3935
+ * Things to know:
3936
+ * - If the width or height in an number this will return the number postfixed with 'px'.
3937
+ * - If the width/height is a percent this will return the percent postfixed with '%'
3938
+ * - Hidden elements have a width of 0 with `window.getComputedStyle`. This function
3939
+ * defaults to the `Component`s `style.width` and falls back to `window.getComputedStyle`.
3940
+ * See [this]{@link http://www.foliotek.com/devblog/getting-the-width-of-a-hidden-element-with-jquery-using-width/}
3941
+ * for more information
3942
+ * - If you want the computed style of the component, use {@link Component#currentWidth}
3943
+ * and {@link {Component#currentHeight}
3944
+ *
3945
+ * @fires Component#componentresize
3946
+ *
3947
+ * @param {string} widthOrHeight
3948
+ 8 'width' or 'height'
3949
+ *
3950
+ * @param {number|string} [num]
3951
+ 8 New dimension
3952
+ *
3953
+ * @param {boolean} [skipListeners]
3954
+ * Skip componentresize event trigger
3955
+ *
3956
+ * @return {number}
3957
+ * The dimension when getting or 0 if unset
3958
+ */
3959
+
3960
+
3961
+ Component.prototype.dimension = function dimension(widthOrHeight, num, skipListeners) {
3962
+ if (num !== undefined) {
3963
+ // Set to zero if null or literally NaN (NaN !== NaN)
3964
+ if (num === null || num !== num) {
3965
+ num = 0;
3966
+ }
3967
+
3968
+ // Check if using css width/height (% or px) and adjust
3969
+ if (('' + num).indexOf('%') !== -1 || ('' + num).indexOf('px') !== -1) {
3970
+ this.el_.style[widthOrHeight] = num;
3971
+ } else if (num === 'auto') {
3972
+ this.el_.style[widthOrHeight] = '';
3973
+ } else {
3974
+ this.el_.style[widthOrHeight] = num + 'px';
3975
+ }
3976
+
3977
+ // skipListeners allows us to avoid triggering the resize event when setting both width and height
3978
+ if (!skipListeners) {
3979
+ /**
3980
+ * Triggered when a component is resized.
3981
+ *
3982
+ * @event Component#componentresize
3983
+ * @type {EventTarget~Event}
3984
+ */
3985
+ this.trigger('componentresize');
3986
+ }
3987
+
3988
+ return;
3989
+ }
3990
+
3991
+ // Not setting a value, so getting it
3992
+ // Make sure element exists
3993
+ if (!this.el_) {
3994
+ return 0;
3995
+ }
3996
+
3997
+ // Get dimension value from style
3998
+ var val = this.el_.style[widthOrHeight];
3999
+ var pxIndex = val.indexOf('px');
4000
+
4001
+ if (pxIndex !== -1) {
4002
+ // Return the pixel value with no 'px'
4003
+ return parseInt(val.slice(0, pxIndex), 10);
4004
+ }
4005
+
4006
+ // No px so using % or no style was set, so falling back to offsetWidth/height
4007
+ // If component has display:none, offset will return 0
4008
+ // TODO: handle display:none and no dimension style using px
4009
+ return parseInt(this.el_['offset' + toTitleCase(widthOrHeight)], 10);
4010
+ };
4011
+
4012
+ /**
4013
+ * Get the width or the height of the `Component` elements computed style. Uses
4014
+ * `window.getComputedStyle`.
4015
+ *
4016
+ * @param {string} widthOrHeight
4017
+ * A string containing 'width' or 'height'. Whichever one you want to get.
4018
+ *
4019
+ * @return {number}
4020
+ * The dimension that gets asked for or 0 if nothing was set
4021
+ * for that dimension.
4022
+ */
4023
+
4024
+
4025
+ Component.prototype.currentDimension = function currentDimension(widthOrHeight) {
4026
+ var computedWidthOrHeight = 0;
4027
+
4028
+ if (widthOrHeight !== 'width' && widthOrHeight !== 'height') {
4029
+ throw new Error('currentDimension only accepts width or height value');
4030
+ }
4031
+
4032
+ if (typeof window_1.getComputedStyle === 'function') {
4033
+ var computedStyle = window_1.getComputedStyle(this.el_);
4034
+
4035
+ computedWidthOrHeight = computedStyle.getPropertyValue(widthOrHeight) || computedStyle[widthOrHeight];
4036
+ }
4037
+
4038
+ // remove 'px' from variable and parse as integer
4039
+ computedWidthOrHeight = parseFloat(computedWidthOrHeight);
4040
+
4041
+ // if the computed value is still 0, it's possible that the browser is lying
4042
+ // and we want to check the offset values.
4043
+ // This code also runs wherever getComputedStyle doesn't exist.
4044
+ if (computedWidthOrHeight === 0) {
4045
+ var rule = 'offset' + toTitleCase(widthOrHeight);
4046
+
4047
+ computedWidthOrHeight = this.el_[rule];
4048
+ }
4049
+
4050
+ return computedWidthOrHeight;
4051
+ };
4052
+
4053
+ /**
4054
+ * An object that contains width and height values of the `Component`s
4055
+ * computed style. Uses `window.getComputedStyle`.
4056
+ *
4057
+ * @typedef {Object} Component~DimensionObject
4058
+ *
4059
+ * @property {number} width
4060
+ * The width of the `Component`s computed style.
4061
+ *
4062
+ * @property {number} height
4063
+ * The height of the `Component`s computed style.
4064
+ */
4065
+
4066
+ /**
4067
+ * Get an object that contains width and height values of the `Component`s
4068
+ * computed style.
4069
+ *
4070
+ * @return {Component~DimensionObject}
4071
+ * The dimensions of the components element
4072
+ */
4073
+
4074
+
4075
+ Component.prototype.currentDimensions = function currentDimensions() {
4076
+ return {
4077
+ width: this.currentDimension('width'),
4078
+ height: this.currentDimension('height')
4079
+ };
4080
+ };
4081
+
4082
+ /**
4083
+ * Get the width of the `Component`s computed style. Uses `window.getComputedStyle`.
4084
+ *
4085
+ * @return {number} width
4086
+ * The width of the `Component`s computed style.
4087
+ */
4088
+
4089
+
4090
+ Component.prototype.currentWidth = function currentWidth() {
4091
+ return this.currentDimension('width');
4092
+ };
4093
+
4094
+ /**
4095
+ * Get the height of the `Component`s computed style. Uses `window.getComputedStyle`.
4096
+ *
4097
+ * @return {number} height
4098
+ * The height of the `Component`s computed style.
4099
+ */
4100
+
4101
+
4102
+ Component.prototype.currentHeight = function currentHeight() {
4103
+ return this.currentDimension('height');
4104
+ };
4105
+
4106
+ /**
4107
+ * Set the focus to this component
4108
+ */
4109
+
4110
+
4111
+ Component.prototype.focus = function focus() {
4112
+ this.el_.focus();
4113
+ };
4114
+
4115
+ /**
4116
+ * Remove the focus from this component
4117
+ */
4118
+
4119
+
4120
+ Component.prototype.blur = function blur() {
4121
+ this.el_.blur();
4122
+ };
4123
+
4124
+ /**
4125
+ * Emit a 'tap' events when touch event support gets detected. This gets used to
4126
+ * support toggling the controls through a tap on the video. They get enabled
4127
+ * because every sub-component would have extra overhead otherwise.
4128
+ *
4129
+ * @private
4130
+ * @fires Component#tap
4131
+ * @listens Component#touchstart
4132
+ * @listens Component#touchmove
4133
+ * @listens Component#touchleave
4134
+ * @listens Component#touchcancel
4135
+ * @listens Component#touchend
4136
+ */
4137
+
4138
+
4139
+ Component.prototype.emitTapEvents = function emitTapEvents() {
4140
+ // Track the start time so we can determine how long the touch lasted
4141
+ var touchStart = 0;
4142
+ var firstTouch = null;
4143
+
4144
+ // Maximum movement allowed during a touch event to still be considered a tap
4145
+ // Other popular libs use anywhere from 2 (hammer.js) to 15,
4146
+ // so 10 seems like a nice, round number.
4147
+ var tapMovementThreshold = 10;
4148
+
4149
+ // The maximum length a touch can be while still being considered a tap
4150
+ var touchTimeThreshold = 200;
4151
+
4152
+ var couldBeTap = void 0;
4153
+
4154
+ this.on('touchstart', function (event) {
4155
+ // If more than one finger, don't consider treating this as a click
4156
+ if (event.touches.length === 1) {
4157
+ // Copy pageX/pageY from the object
4158
+ firstTouch = {
4159
+ pageX: event.touches[0].pageX,
4160
+ pageY: event.touches[0].pageY
4161
+ };
4162
+ // Record start time so we can detect a tap vs. "touch and hold"
4163
+ touchStart = new Date().getTime();
4164
+ // Reset couldBeTap tracking
4165
+ couldBeTap = true;
4166
+ }
4167
+ });
4168
+
4169
+ this.on('touchmove', function (event) {
4170
+ // If more than one finger, don't consider treating this as a click
4171
+ if (event.touches.length > 1) {
4172
+ couldBeTap = false;
4173
+ } else if (firstTouch) {
4174
+ // Some devices will throw touchmoves for all but the slightest of taps.
4175
+ // So, if we moved only a small distance, this could still be a tap
4176
+ var xdiff = event.touches[0].pageX - firstTouch.pageX;
4177
+ var ydiff = event.touches[0].pageY - firstTouch.pageY;
4178
+ var touchDistance = Math.sqrt(xdiff * xdiff + ydiff * ydiff);
4179
+
4180
+ if (touchDistance > tapMovementThreshold) {
4181
+ couldBeTap = false;
4182
+ }
4183
+ }
4184
+ });
4185
+
4186
+ var noTap = function noTap() {
4187
+ couldBeTap = false;
4188
+ };
4189
+
4190
+ // TODO: Listen to the original target. http://youtu.be/DujfpXOKUp8?t=13m8s
4191
+ this.on('touchleave', noTap);
4192
+ this.on('touchcancel', noTap);
4193
+
4194
+ // When the touch ends, measure how long it took and trigger the appropriate
4195
+ // event
4196
+ this.on('touchend', function (event) {
4197
+ firstTouch = null;
4198
+ // Proceed only if the touchmove/leave/cancel event didn't happen
4199
+ if (couldBeTap === true) {
4200
+ // Measure how long the touch lasted
4201
+ var touchTime = new Date().getTime() - touchStart;
4202
+
4203
+ // Make sure the touch was less than the threshold to be considered a tap
4204
+ if (touchTime < touchTimeThreshold) {
4205
+ // Don't let browser turn this into a click
4206
+ event.preventDefault();
4207
+ /**
4208
+ * Triggered when a `Component` is tapped.
4209
+ *
4210
+ * @event Component#tap
4211
+ * @type {EventTarget~Event}
4212
+ */
4213
+ this.trigger('tap');
4214
+ // It may be good to copy the touchend event object and change the
4215
+ // type to tap, if the other event properties aren't exact after
4216
+ // Events.fixEvent runs (e.g. event.target)
4217
+ }
4218
+ }
4219
+ });
4220
+ };
4221
+
4222
+ /**
4223
+ * This function reports user activity whenever touch events happen. This can get
4224
+ * turned off by any sub-components that wants touch events to act another way.
4225
+ *
4226
+ * Report user touch activity when touch events occur. User activity gets used to
4227
+ * determine when controls should show/hide. It is simple when it comes to mouse
4228
+ * events, because any mouse event should show the controls. So we capture mouse
4229
+ * events that bubble up to the player and report activity when that happens.
4230
+ * With touch events it isn't as easy as `touchstart` and `touchend` toggle player
4231
+ * controls. So touch events can't help us at the player level either.
4232
+ *
4233
+ * User activity gets checked asynchronously. So what could happen is a tap event
4234
+ * on the video turns the controls off. Then the `touchend` event bubbles up to
4235
+ * the player. Which, if it reported user activity, would turn the controls right
4236
+ * back on. We also don't want to completely block touch events from bubbling up.
4237
+ * Furthermore a `touchmove` event and anything other than a tap, should not turn
4238
+ * controls back on.
4239
+ *
4240
+ * @listens Component#touchstart
4241
+ * @listens Component#touchmove
4242
+ * @listens Component#touchend
4243
+ * @listens Component#touchcancel
4244
+ */
4245
+
4246
+
4247
+ Component.prototype.enableTouchActivity = function enableTouchActivity() {
4248
+ // Don't continue if the root player doesn't support reporting user activity
4249
+ if (!this.player() || !this.player().reportUserActivity) {
4250
+ return;
4251
+ }
4252
+
4253
+ // listener for reporting that the user is active
4254
+ var report = bind(this.player(), this.player().reportUserActivity);
4255
+
4256
+ var touchHolding = void 0;
4257
+
4258
+ this.on('touchstart', function () {
4259
+ report();
4260
+ // For as long as the they are touching the device or have their mouse down,
4261
+ // we consider them active even if they're not moving their finger or mouse.
4262
+ // So we want to continue to update that they are active
4263
+ this.clearInterval(touchHolding);
4264
+ // report at the same interval as activityCheck
4265
+ touchHolding = this.setInterval(report, 250);
4266
+ });
4267
+
4268
+ var touchEnd = function touchEnd(event) {
4269
+ report();
4270
+ // stop the interval that maintains activity if the touch is holding
4271
+ this.clearInterval(touchHolding);
4272
+ };
4273
+
4274
+ this.on('touchmove', report);
4275
+ this.on('touchend', touchEnd);
4276
+ this.on('touchcancel', touchEnd);
4277
+ };
4278
+
4279
+ /**
4280
+ * A callback that has no parameters and is bound into `Component`s context.
4281
+ *
4282
+ * @callback Component~GenericCallback
4283
+ * @this Component
4284
+ */
4285
+
4286
+ /**
4287
+ * Creates a function that runs after an `x` millisecond timeout. This function is a
4288
+ * wrapper around `window.setTimeout`. There are a few reasons to use this one
4289
+ * instead though:
4290
+ * 1. It gets cleared via {@link Component#clearTimeout} when
4291
+ * {@link Component#dispose} gets called.
4292
+ * 2. The function callback will gets turned into a {@link Component~GenericCallback}
4293
+ *
4294
+ * > Note: You can't use `window.clearTimeout` on the id returned by this function. This
4295
+ * will cause its dispose listener not to get cleaned up! Please use
4296
+ * {@link Component#clearTimeout} or {@link Component#dispose} instead.
4297
+ *
4298
+ * @param {Component~GenericCallback} fn
4299
+ * The function that will be run after `timeout`.
4300
+ *
4301
+ * @param {number} timeout
4302
+ * Timeout in milliseconds to delay before executing the specified function.
4303
+ *
4304
+ * @return {number}
4305
+ * Returns a timeout ID that gets used to identify the timeout. It can also
4306
+ * get used in {@link Component#clearTimeout} to clear the timeout that
4307
+ * was set.
4308
+ *
4309
+ * @listens Component#dispose
4310
+ * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/setTimeout}
4311
+ */
4312
+
4313
+
4314
+ Component.prototype.setTimeout = function setTimeout(fn, timeout) {
4315
+ var _this2 = this;
4316
+
4317
+ // declare as variables so they are properly available in timeout function
4318
+ // eslint-disable-next-line
4319
+ var timeoutId, disposeFn;
4320
+
4321
+ fn = bind(this, fn);
4322
+
4323
+ timeoutId = window_1.setTimeout(function () {
4324
+ _this2.off('dispose', disposeFn);
4325
+ fn();
4326
+ }, timeout);
4327
+
4328
+ disposeFn = function disposeFn() {
4329
+ return _this2.clearTimeout(timeoutId);
4330
+ };
4331
+
4332
+ disposeFn.guid = 'vjs-timeout-' + timeoutId;
4333
+
4334
+ this.on('dispose', disposeFn);
4335
+
4336
+ return timeoutId;
4337
+ };
4338
+
4339
+ /**
4340
+ * Clears a timeout that gets created via `window.setTimeout` or
4341
+ * {@link Component#setTimeout}. If you set a timeout via {@link Component#setTimeout}
4342
+ * use this function instead of `window.clearTimout`. If you don't your dispose
4343
+ * listener will not get cleaned up until {@link Component#dispose}!
4344
+ *
4345
+ * @param {number} timeoutId
4346
+ * The id of the timeout to clear. The return value of
4347
+ * {@link Component#setTimeout} or `window.setTimeout`.
4348
+ *
4349
+ * @return {number}
4350
+ * Returns the timeout id that was cleared.
4351
+ *
4352
+ * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/clearTimeout}
4353
+ */
4354
+
4355
+
4356
+ Component.prototype.clearTimeout = function clearTimeout(timeoutId) {
4357
+ window_1.clearTimeout(timeoutId);
4358
+
4359
+ var disposeFn = function disposeFn() {};
4360
+
4361
+ disposeFn.guid = 'vjs-timeout-' + timeoutId;
4362
+
4363
+ this.off('dispose', disposeFn);
4364
+
4365
+ return timeoutId;
4366
+ };
4367
+
4368
+ /**
4369
+ * Creates a function that gets run every `x` milliseconds. This function is a wrapper
4370
+ * around `window.setInterval`. There are a few reasons to use this one instead though.
4371
+ * 1. It gets cleared via {@link Component#clearInterval} when
4372
+ * {@link Component#dispose} gets called.
4373
+ * 2. The function callback will be a {@link Component~GenericCallback}
4374
+ *
4375
+ * @param {Component~GenericCallback} fn
4376
+ * The function to run every `x` seconds.
4377
+ *
4378
+ * @param {number} interval
4379
+ * Execute the specified function every `x` milliseconds.
4380
+ *
4381
+ * @return {number}
4382
+ * Returns an id that can be used to identify the interval. It can also be be used in
4383
+ * {@link Component#clearInterval} to clear the interval.
4384
+ *
4385
+ * @listens Component#dispose
4386
+ * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/setInterval}
4387
+ */
4388
+
4389
+
4390
+ Component.prototype.setInterval = function setInterval(fn, interval) {
4391
+ var _this3 = this;
4392
+
4393
+ fn = bind(this, fn);
4394
+
4395
+ var intervalId = window_1.setInterval(fn, interval);
4396
+
4397
+ var disposeFn = function disposeFn() {
4398
+ return _this3.clearInterval(intervalId);
4399
+ };
4400
+
4401
+ disposeFn.guid = 'vjs-interval-' + intervalId;
4402
+
4403
+ this.on('dispose', disposeFn);
4404
+
4405
+ return intervalId;
4406
+ };
4407
+
4408
+ /**
4409
+ * Clears an interval that gets created via `window.setInterval` or
4410
+ * {@link Component#setInterval}. If you set an inteval via {@link Component#setInterval}
4411
+ * use this function instead of `window.clearInterval`. If you don't your dispose
4412
+ * listener will not get cleaned up until {@link Component#dispose}!
4413
+ *
4414
+ * @param {number} intervalId
4415
+ * The id of the interval to clear. The return value of
4416
+ * {@link Component#setInterval} or `window.setInterval`.
4417
+ *
4418
+ * @return {number}
4419
+ * Returns the interval id that was cleared.
4420
+ *
4421
+ * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/clearInterval}
4422
+ */
4423
+
4424
+
4425
+ Component.prototype.clearInterval = function clearInterval(intervalId) {
4426
+ window_1.clearInterval(intervalId);
4427
+
4428
+ var disposeFn = function disposeFn() {};
4429
+
4430
+ disposeFn.guid = 'vjs-interval-' + intervalId;
4431
+
4432
+ this.off('dispose', disposeFn);
4433
+
4434
+ return intervalId;
4435
+ };
4436
+
4437
+ /**
4438
+ * Queues up a callback to be passed to requestAnimationFrame (rAF), but
4439
+ * with a few extra bonuses:
4440
+ *
4441
+ * - Supports browsers that do not support rAF by falling back to
4442
+ * {@link Component#setTimeout}.
4443
+ *
4444
+ * - The callback is turned into a {@link Component~GenericCallback} (i.e.
4445
+ * bound to the component).
4446
+ *
4447
+ * - Automatic cancellation of the rAF callback is handled if the component
4448
+ * is disposed before it is called.
4449
+ *
4450
+ * @param {Component~GenericCallback} fn
4451
+ * A function that will be bound to this component and executed just
4452
+ * before the browser's next repaint.
4453
+ *
4454
+ * @return {number}
4455
+ * Returns an rAF ID that gets used to identify the timeout. It can
4456
+ * also be used in {@link Component#cancelAnimationFrame} to cancel
4457
+ * the animation frame callback.
4458
+ *
4459
+ * @listens Component#dispose
4460
+ * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame}
4461
+ */
4462
+
4463
+
4464
+ Component.prototype.requestAnimationFrame = function requestAnimationFrame(fn) {
4465
+ var _this4 = this;
4466
+
4467
+ // declare as variables so they are properly available in rAF function
4468
+ // eslint-disable-next-line
4469
+ var id, disposeFn;
4470
+
4471
+ if (this.supportsRaf_) {
4472
+ fn = bind(this, fn);
4473
+
4474
+ id = window_1.requestAnimationFrame(function () {
4475
+ _this4.off('dispose', disposeFn);
4476
+ fn();
4477
+ });
4478
+
4479
+ disposeFn = function disposeFn() {
4480
+ return _this4.cancelAnimationFrame(id);
4481
+ };
4482
+
4483
+ disposeFn.guid = 'vjs-raf-' + id;
4484
+ this.on('dispose', disposeFn);
4485
+
4486
+ return id;
4487
+ }
4488
+
4489
+ // Fall back to using a timer.
4490
+ return this.setTimeout(fn, 1000 / 60);
4491
+ };
4492
+
4493
+ /**
4494
+ * Cancels a queued callback passed to {@link Component#requestAnimationFrame}
4495
+ * (rAF).
4496
+ *
4497
+ * If you queue an rAF callback via {@link Component#requestAnimationFrame},
4498
+ * use this function instead of `window.cancelAnimationFrame`. If you don't,
4499
+ * your dispose listener will not get cleaned up until {@link Component#dispose}!
4500
+ *
4501
+ * @param {number} id
4502
+ * The rAF ID to clear. The return value of {@link Component#requestAnimationFrame}.
4503
+ *
4504
+ * @return {number}
4505
+ * Returns the rAF ID that was cleared.
4506
+ *
4507
+ * @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/window/cancelAnimationFrame}
4508
+ */
4509
+
4510
+
4511
+ Component.prototype.cancelAnimationFrame = function cancelAnimationFrame(id) {
4512
+ if (this.supportsRaf_) {
4513
+ window_1.cancelAnimationFrame(id);
4514
+
4515
+ var disposeFn = function disposeFn() {};
4516
+
4517
+ disposeFn.guid = 'vjs-raf-' + id;
4518
+
4519
+ this.off('dispose', disposeFn);
4520
+
4521
+ return id;
4522
+ }
4523
+
4524
+ // Fall back to using a timer.
4525
+ return this.clearTimeout(id);
4526
+ };
4527
+
4528
+ /**
4529
+ * Register a `Component` with `videojs` given the name and the component.
4530
+ *
4531
+ * > NOTE: {@link Tech}s should not be registered as a `Component`. {@link Tech}s
4532
+ * should be registered using {@link Tech.registerTech} or
4533
+ * {@link videojs:videojs.registerTech}.
4534
+ *
4535
+ * > NOTE: This function can also be seen on videojs as
4536
+ * {@link videojs:videojs.registerComponent}.
4537
+ *
4538
+ * @param {string} name
4539
+ * The name of the `Component` to register.
4540
+ *
4541
+ * @param {Component} ComponentToRegister
4542
+ * The `Component` class to register.
4543
+ *
4544
+ * @return {Component}
4545
+ * The `Component` that was registered.
4546
+ */
4547
+
4548
+
4549
+ Component.registerComponent = function registerComponent(name, ComponentToRegister) {
4550
+ if (typeof name !== 'string' || !name) {
4551
+ throw new Error('Illegal component name, "' + name + '"; must be a non-empty string.');
4552
+ }
4553
+
4554
+ var Tech = Component.getComponent('Tech');
4555
+
4556
+ // We need to make sure this check is only done if Tech has been registered.
4557
+ var isTech = Tech && Tech.isTech(ComponentToRegister);
4558
+ var isComp = Component === ComponentToRegister || Component.prototype.isPrototypeOf(ComponentToRegister.prototype);
4559
+
4560
+ if (isTech || !isComp) {
4561
+ var reason = void 0;
4562
+
4563
+ if (isTech) {
4564
+ reason = 'techs must be registered using Tech.registerTech()';
4565
+ } else {
4566
+ reason = 'must be a Component subclass';
4567
+ }
4568
+
4569
+ throw new Error('Illegal component, "' + name + '"; ' + reason + '.');
4570
+ }
4571
+
4572
+ name = toTitleCase(name);
4573
+
4574
+ if (!Component.components_) {
4575
+ Component.components_ = {};
4576
+ }
4577
+
4578
+ var Player = Component.getComponent('Player');
4579
+
4580
+ if (name === 'Player' && Player && Player.players) {
4581
+ var players = Player.players;
4582
+ var playerNames = Object.keys(players);
4583
+
4584
+ // If we have players that were disposed, then their name will still be
4585
+ // in Players.players. So, we must loop through and verify that the value
4586
+ // for each item is not null. This allows registration of the Player component
4587
+ // after all players have been disposed or before any were created.
4588
+ if (players && playerNames.length > 0 && playerNames.map(function (pname) {
4589
+ return players[pname];
4590
+ }).every(Boolean)) {
4591
+ throw new Error('Can not register Player component after player has been created.');
4592
+ }
4593
+ }
4594
+
4595
+ Component.components_[name] = ComponentToRegister;
4596
+
4597
+ return ComponentToRegister;
4598
+ };
4599
+
4600
+ /**
4601
+ * Get a `Component` based on the name it was registered with.
4602
+ *
4603
+ * @param {string} name
4604
+ * The Name of the component to get.
4605
+ *
4606
+ * @return {Component}
4607
+ * The `Component` that got registered under the given name.
4608
+ *
4609
+ * @deprecated In `videojs` 6 this will not return `Component`s that were not
4610
+ * registered using {@link Component.registerComponent}. Currently we
4611
+ * check the global `videojs` object for a `Component` name and
4612
+ * return that if it exists.
4613
+ */
4614
+
4615
+
4616
+ Component.getComponent = function getComponent(name) {
4617
+ if (!name) {
4618
+ return;
4619
+ }
4620
+
4621
+ name = toTitleCase(name);
4622
+
4623
+ if (Component.components_ && Component.components_[name]) {
4624
+ return Component.components_[name];
4625
+ }
4626
+ };
4627
+
4628
+ return Component;
4629
+ }();
4630
+
4631
+ /**
4632
+ * Whether or not this component supports `requestAnimationFrame`.
4633
+ *
4634
+ * This is exposed primarily for testing purposes.
4635
+ *
4636
+ * @private
4637
+ * @type {Boolean}
4638
+ */
4639
+
4640
+
4641
+ Component.prototype.supportsRaf_ = typeof window_1.requestAnimationFrame === 'function' && typeof window_1.cancelAnimationFrame === 'function';
4642
+
4643
+ Component.registerComponent('Component', Component);
4644
+
4645
+ /**
4646
+ * @file browser.js
4647
+ * @module browser
4648
+ */
4649
+
4650
+ var USER_AGENT = window_1.navigator && window_1.navigator.userAgent || '';
4651
+ var webkitVersionMap = /AppleWebKit\/([\d.]+)/i.exec(USER_AGENT);
4652
+ var appleWebkitVersion = webkitVersionMap ? parseFloat(webkitVersionMap.pop()) : null;
4653
+
4654
+ /*
4655
+ * Device is an iPhone
4656
+ *
4657
+ * @type {Boolean}
4658
+ * @constant
4659
+ * @private
4660
+ */
4661
+ var IS_IPAD = /iPad/i.test(USER_AGENT);
4662
+
4663
+ // The Facebook app's UIWebView identifies as both an iPhone and iPad, so
4664
+ // to identify iPhones, we need to exclude iPads.
4665
+ // http://artsy.github.io/blog/2012/10/18/the-perils-of-ios-user-agent-sniffing/
4666
+ var IS_IPHONE = /iPhone/i.test(USER_AGENT) && !IS_IPAD;
4667
+ var IS_IPOD = /iPod/i.test(USER_AGENT);
4668
+ var IS_IOS = IS_IPHONE || IS_IPAD || IS_IPOD;
4669
+
4670
+ var IOS_VERSION = function () {
4671
+ var match = USER_AGENT.match(/OS (\d+)_/i);
4672
+
4673
+ if (match && match[1]) {
4674
+ return match[1];
4675
+ }
4676
+ return null;
4677
+ }();
4678
+
4679
+ var IS_ANDROID = /Android/i.test(USER_AGENT);
4680
+ var ANDROID_VERSION = function () {
4681
+ // This matches Android Major.Minor.Patch versions
4682
+ // ANDROID_VERSION is Major.Minor as a Number, if Minor isn't available, then only Major is returned
4683
+ var match = USER_AGENT.match(/Android (\d+)(?:\.(\d+))?(?:\.(\d+))*/i);
4684
+
4685
+ if (!match) {
4686
+ return null;
4687
+ }
4688
+
4689
+ var major = match[1] && parseFloat(match[1]);
4690
+ var minor = match[2] && parseFloat(match[2]);
4691
+
4692
+ if (major && minor) {
4693
+ return parseFloat(match[1] + '.' + match[2]);
4694
+ } else if (major) {
4695
+ return major;
4696
+ }
4697
+ return null;
4698
+ }();
4699
+
4700
+ var IS_NATIVE_ANDROID = IS_ANDROID && ANDROID_VERSION < 5 && appleWebkitVersion < 537;
4701
+
4702
+ var IS_FIREFOX = /Firefox/i.test(USER_AGENT);
4703
+ var IS_EDGE = /Edge/i.test(USER_AGENT);
4704
+ var IS_CHROME = !IS_EDGE && (/Chrome/i.test(USER_AGENT) || /CriOS/i.test(USER_AGENT));
4705
+ var CHROME_VERSION = function () {
4706
+ var match = USER_AGENT.match(/(Chrome|CriOS)\/(\d+)/);
4707
+
4708
+ if (match && match[2]) {
4709
+ return parseFloat(match[2]);
4710
+ }
4711
+ return null;
4712
+ }();
4713
+ var IE_VERSION = function () {
4714
+ var result = /MSIE\s(\d+)\.\d/.exec(USER_AGENT);
4715
+ var version = result && parseFloat(result[1]);
4716
+
4717
+ if (!version && /Trident\/7.0/i.test(USER_AGENT) && /rv:11.0/.test(USER_AGENT)) {
4718
+ // IE 11 has a different user agent string than other IE versions
4719
+ version = 11.0;
4720
+ }
4721
+
4722
+ return version;
4723
+ }();
4724
+
4725
+ var IS_SAFARI = /Safari/i.test(USER_AGENT) && !IS_CHROME && !IS_ANDROID && !IS_EDGE;
4726
+ var IS_ANY_SAFARI = (IS_SAFARI || IS_IOS) && !IS_CHROME;
4727
+
4728
+ var TOUCH_ENABLED = isReal() && ('ontouchstart' in window_1 || window_1.navigator.maxTouchPoints || window_1.DocumentTouch && window_1.document instanceof window_1.DocumentTouch);
4729
+
4730
+ var browser = /*#__PURE__*/Object.freeze({
4731
+ IS_IPAD: IS_IPAD,
4732
+ IS_IPHONE: IS_IPHONE,
4733
+ IS_IPOD: IS_IPOD,
4734
+ IS_IOS: IS_IOS,
4735
+ IOS_VERSION: IOS_VERSION,
4736
+ IS_ANDROID: IS_ANDROID,
4737
+ ANDROID_VERSION: ANDROID_VERSION,
4738
+ IS_NATIVE_ANDROID: IS_NATIVE_ANDROID,
4739
+ IS_FIREFOX: IS_FIREFOX,
4740
+ IS_EDGE: IS_EDGE,
4741
+ IS_CHROME: IS_CHROME,
4742
+ CHROME_VERSION: CHROME_VERSION,
4743
+ IE_VERSION: IE_VERSION,
4744
+ IS_SAFARI: IS_SAFARI,
4745
+ IS_ANY_SAFARI: IS_ANY_SAFARI,
4746
+ TOUCH_ENABLED: TOUCH_ENABLED
4747
+ });
4748
+
4749
+ /**
4750
+ * @file time-ranges.js
4751
+ * @module time-ranges
4752
+ */
4753
+
4754
+ /**
4755
+ * Returns the time for the specified index at the start or end
4756
+ * of a TimeRange object.
4757
+ *
4758
+ * @function time-ranges:indexFunction
4759
+ *
4760
+ * @param {number} [index=0]
4761
+ * The range number to return the time for.
4762
+ *
4763
+ * @return {number}
4764
+ * The time that offset at the specified index.
4765
+ *
4766
+ * @depricated index must be set to a value, in the future this will throw an error.
4767
+ */
4768
+
4769
+ /**
4770
+ * An object that contains ranges of time for various reasons.
4771
+ *
4772
+ * @typedef {Object} TimeRange
4773
+ *
4774
+ * @property {number} length
4775
+ * The number of time ranges represented by this Object
4776
+ *
4777
+ * @property {time-ranges:indexFunction} start
4778
+ * Returns the time offset at which a specified time range begins.
4779
+ *
4780
+ * @property {time-ranges:indexFunction} end
4781
+ * Returns the time offset at which a specified time range ends.
4782
+ *
4783
+ * @see https://developer.mozilla.org/en-US/docs/Web/API/TimeRanges
4784
+ */
4785
+
4786
+ /**
4787
+ * Check if any of the time ranges are over the maximum index.
4788
+ *
4789
+ * @param {string} fnName
4790
+ * The function name to use for logging
4791
+ *
4792
+ * @param {number} index
4793
+ * The index to check
4794
+ *
4795
+ * @param {number} maxIndex
4796
+ * The maximum possible index
4797
+ *
4798
+ * @throws {Error} if the timeRanges provided are over the maxIndex
4799
+ */
4800
+ function rangeCheck(fnName, index, maxIndex) {
4801
+ if (typeof index !== 'number' || index < 0 || index > maxIndex) {
4802
+ throw new Error('Failed to execute \'' + fnName + '\' on \'TimeRanges\': The index provided (' + index + ') is non-numeric or out of bounds (0-' + maxIndex + ').');
4803
+ }
4804
+ }
4805
+
4806
+ /**
4807
+ * Get the time for the specified index at the start or end
4808
+ * of a TimeRange object.
4809
+ *
4810
+ * @param {string} fnName
4811
+ * The function name to use for logging
4812
+ *
4813
+ * @param {string} valueIndex
4814
+ * The property that should be used to get the time. should be 'start' or 'end'
4815
+ *
4816
+ * @param {Array} ranges
4817
+ * An array of time ranges
4818
+ *
4819
+ * @param {Array} [rangeIndex=0]
4820
+ * The index to start the search at
4821
+ *
4822
+ * @return {number}
4823
+ * The time that offset at the specified index.
4824
+ *
4825
+ *
4826
+ * @depricated rangeIndex must be set to a value, in the future this will throw an error.
4827
+ * @throws {Error} if rangeIndex is more than the length of ranges
4828
+ */
4829
+ function getRange(fnName, valueIndex, ranges, rangeIndex) {
4830
+ rangeCheck(fnName, rangeIndex, ranges.length - 1);
4831
+ return ranges[rangeIndex][valueIndex];
4832
+ }
4833
+
4834
+ /**
4835
+ * Create a time range object given ranges of time.
4836
+ *
4837
+ * @param {Array} [ranges]
4838
+ * An array of time ranges.
4839
+ */
4840
+ function createTimeRangesObj(ranges) {
4841
+ if (ranges === undefined || ranges.length === 0) {
4842
+ return {
4843
+ length: 0,
4844
+ start: function start() {
4845
+ throw new Error('This TimeRanges object is empty');
4846
+ },
4847
+ end: function end() {
4848
+ throw new Error('This TimeRanges object is empty');
4849
+ }
4850
+ };
4851
+ }
4852
+ return {
4853
+ length: ranges.length,
4854
+ start: getRange.bind(null, 'start', 0, ranges),
4855
+ end: getRange.bind(null, 'end', 1, ranges)
4856
+ };
4857
+ }
4858
+
4859
+ /**
4860
+ * Should create a fake `TimeRange` object which mimics an HTML5 time range instance.
4861
+ *
4862
+ * @param {number|Array} start
4863
+ * The start of a single range or an array of ranges
4864
+ *
4865
+ * @param {number} end
4866
+ * The end of a single range.
4867
+ *
4868
+ * @private
4869
+ */
4870
+ function createTimeRanges(start, end) {
4871
+ if (Array.isArray(start)) {
4872
+ return createTimeRangesObj(start);
4873
+ } else if (start === undefined || end === undefined) {
4874
+ return createTimeRangesObj();
4875
+ }
4876
+ return createTimeRangesObj([[start, end]]);
4877
+ }
4878
+
4879
+ /**
4880
+ * @file buffer.js
4881
+ * @module buffer
4882
+ */
4883
+
4884
+ /**
4885
+ * Compute the percentage of the media that has been buffered.
4886
+ *
4887
+ * @param {TimeRange} buffered
4888
+ * The current `TimeRange` object representing buffered time ranges
4889
+ *
4890
+ * @param {number} duration
4891
+ * Total duration of the media
4892
+ *
4893
+ * @return {number}
4894
+ * Percent buffered of the total duration in decimal form.
4895
+ */
4896
+ function bufferedPercent(buffered, duration) {
4897
+ var bufferedDuration = 0;
4898
+ var start = void 0;
4899
+ var end = void 0;
4900
+
4901
+ if (!duration) {
4902
+ return 0;
4903
+ }
4904
+
4905
+ if (!buffered || !buffered.length) {
4906
+ buffered = createTimeRanges(0, 0);
4907
+ }
4908
+
4909
+ for (var i = 0; i < buffered.length; i++) {
4910
+ start = buffered.start(i);
4911
+ end = buffered.end(i);
4912
+
4913
+ // buffered end can be bigger than duration by a very small fraction
4914
+ if (end > duration) {
4915
+ end = duration;
4916
+ }
4917
+
4918
+ bufferedDuration += end - start;
4919
+ }
4920
+
4921
+ return bufferedDuration / duration;
4922
+ }
4923
+
4924
+ /**
4925
+ * @file fullscreen-api.js
4926
+ * @module fullscreen-api
4927
+ * @private
4928
+ */
4929
+
4930
+ /**
4931
+ * Store the browser-specific methods for the fullscreen API.
4932
+ *
4933
+ * @type {Object}
4934
+ * @see [Specification]{@link https://fullscreen.spec.whatwg.org}
4935
+ * @see [Map Approach From Screenfull.js]{@link https://github.com/sindresorhus/screenfull.js}
4936
+ */
4937
+ var FullscreenApi = {};
4938
+
4939
+ // browser API methods
4940
+ var apiMap = [['requestFullscreen', 'exitFullscreen', 'fullscreenElement', 'fullscreenEnabled', 'fullscreenchange', 'fullscreenerror'],
4941
+ // WebKit
4942
+ ['webkitRequestFullscreen', 'webkitExitFullscreen', 'webkitFullscreenElement', 'webkitFullscreenEnabled', 'webkitfullscreenchange', 'webkitfullscreenerror'],
4943
+ // Old WebKit (Safari 5.1)
4944
+ ['webkitRequestFullScreen', 'webkitCancelFullScreen', 'webkitCurrentFullScreenElement', 'webkitCancelFullScreen', 'webkitfullscreenchange', 'webkitfullscreenerror'],
4945
+ // Mozilla
4946
+ ['mozRequestFullScreen', 'mozCancelFullScreen', 'mozFullScreenElement', 'mozFullScreenEnabled', 'mozfullscreenchange', 'mozfullscreenerror'],
4947
+ // Microsoft
4948
+ ['msRequestFullscreen', 'msExitFullscreen', 'msFullscreenElement', 'msFullscreenEnabled', 'MSFullscreenChange', 'MSFullscreenError']];
4949
+
4950
+ var specApi = apiMap[0];
4951
+ var browserApi = void 0;
4952
+
4953
+ // determine the supported set of functions
4954
+ for (var i = 0; i < apiMap.length; i++) {
4955
+ // check for exitFullscreen function
4956
+ if (apiMap[i][1] in document_1) {
4957
+ browserApi = apiMap[i];
4958
+ break;
4959
+ }
4960
+ }
4961
+
4962
+ // map the browser API names to the spec API names
4963
+ if (browserApi) {
4964
+ for (var _i = 0; _i < browserApi.length; _i++) {
4965
+ FullscreenApi[specApi[_i]] = browserApi[_i];
4966
+ }
4967
+ }
4968
+
4969
+ /**
4970
+ * @file media-error.js
4971
+ */
4972
+
4973
+ /**
4974
+ * A Custom `MediaError` class which mimics the standard HTML5 `MediaError` class.
4975
+ *
4976
+ * @param {number|string|Object|MediaError} value
4977
+ * This can be of multiple types:
4978
+ * - number: should be a standard error code
4979
+ * - string: an error message (the code will be 0)
4980
+ * - Object: arbitrary properties
4981
+ * - `MediaError` (native): used to populate a video.js `MediaError` object
4982
+ * - `MediaError` (video.js): will return itself if it's already a
4983
+ * video.js `MediaError` object.
4984
+ *
4985
+ * @see [MediaError Spec]{@link https://dev.w3.org/html5/spec-author-view/video.html#mediaerror}
4986
+ * @see [Encrypted MediaError Spec]{@link https://www.w3.org/TR/2013/WD-encrypted-media-20130510/#error-codes}
4987
+ *
4988
+ * @class MediaError
4989
+ */
4990
+ function MediaError(value) {
4991
+
4992
+ // Allow redundant calls to this constructor to avoid having `instanceof`
4993
+ // checks peppered around the code.
4994
+ if (value instanceof MediaError) {
4995
+ return value;
4996
+ }
4997
+
4998
+ if (typeof value === 'number') {
4999
+ this.code = value;
5000
+ } else if (typeof value === 'string') {
5001
+ // default code is zero, so this is a custom error
5002
+ this.message = value;
5003
+ } else if (isObject(value)) {
5004
+
5005
+ // We assign the `code` property manually because native `MediaError` objects
5006
+ // do not expose it as an own/enumerable property of the object.
5007
+ if (typeof value.code === 'number') {
5008
+ this.code = value.code;
5009
+ }
5010
+
5011
+ assign(this, value);
5012
+ }
5013
+
5014
+ if (!this.message) {
5015
+ this.message = MediaError.defaultMessages[this.code] || '';
5016
+ }
5017
+ }
5018
+
5019
+ /**
5020
+ * The error code that refers two one of the defined `MediaError` types
5021
+ *
5022
+ * @type {Number}
5023
+ */
5024
+ MediaError.prototype.code = 0;
5025
+
5026
+ /**
5027
+ * An optional message that to show with the error. Message is not part of the HTML5
5028
+ * video spec but allows for more informative custom errors.
5029
+ *
5030
+ * @type {String}
5031
+ */
5032
+ MediaError.prototype.message = '';
5033
+
5034
+ /**
5035
+ * An optional status code that can be set by plugins to allow even more detail about
5036
+ * the error. For example a plugin might provide a specific HTTP status code and an
5037
+ * error message for that code. Then when the plugin gets that error this class will
5038
+ * know how to display an error message for it. This allows a custom message to show
5039
+ * up on the `Player` error overlay.
5040
+ *
5041
+ * @type {Array}
5042
+ */
5043
+ MediaError.prototype.status = null;
5044
+
5045
+ /**
5046
+ * Errors indexed by the W3C standard. The order **CANNOT CHANGE**! See the
5047
+ * specification listed under {@link MediaError} for more information.
5048
+ *
5049
+ * @enum {array}
5050
+ * @readonly
5051
+ * @property {string} 0 - MEDIA_ERR_CUSTOM
5052
+ * @property {string} 1 - MEDIA_ERR_CUSTOM
5053
+ * @property {string} 2 - MEDIA_ERR_ABORTED
5054
+ * @property {string} 3 - MEDIA_ERR_NETWORK
5055
+ * @property {string} 4 - MEDIA_ERR_SRC_NOT_SUPPORTED
5056
+ * @property {string} 5 - MEDIA_ERR_ENCRYPTED
5057
+ */
5058
+ MediaError.errorTypes = ['MEDIA_ERR_CUSTOM', 'MEDIA_ERR_ABORTED', 'MEDIA_ERR_NETWORK', 'MEDIA_ERR_DECODE', 'MEDIA_ERR_SRC_NOT_SUPPORTED', 'MEDIA_ERR_ENCRYPTED'];
5059
+
5060
+ /**
5061
+ * The default `MediaError` messages based on the {@link MediaError.errorTypes}.
5062
+ *
5063
+ * @type {Array}
5064
+ * @constant
5065
+ */
5066
+ MediaError.defaultMessages = {
5067
+ 1: 'You aborted the media playback',
5068
+ 2: 'A network error caused the media download to fail part-way.',
5069
+ 3: 'The media playback was aborted due to a corruption problem or because the media used features your browser did not support.',
5070
+ 4: 'The media could not be loaded, either because the server or network failed or because the format is not supported.',
5071
+ 5: 'The media is encrypted and we do not have the keys to decrypt it.'
5072
+ };
5073
+
5074
+ // Add types as properties on MediaError
5075
+ // e.g. MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED = 4;
5076
+ for (var errNum = 0; errNum < MediaError.errorTypes.length; errNum++) {
5077
+ MediaError[MediaError.errorTypes[errNum]] = errNum;
5078
+ // values should be accessible on both the class and instance
5079
+ MediaError.prototype[MediaError.errorTypes[errNum]] = errNum;
5080
+ }
5081
+
5082
+ var tuple = SafeParseTuple;
5083
+
5084
+ function SafeParseTuple(obj, reviver) {
5085
+ var json;
5086
+ var error = null;
5087
+
5088
+ try {
5089
+ json = JSON.parse(obj, reviver);
5090
+ } catch (err) {
5091
+ error = err;
5092
+ }
5093
+
5094
+ return [error, json];
5095
+ }
5096
+
5097
+ /**
5098
+ * Returns whether an object is `Promise`-like (i.e. has a `then` method).
5099
+ *
5100
+ * @param {Object} value
5101
+ * An object that may or may not be `Promise`-like.
5102
+ *
5103
+ * @return {Boolean}
5104
+ * Whether or not the object is `Promise`-like.
5105
+ */
5106
+ function isPromise(value) {
5107
+ return value !== undefined && value !== null && typeof value.then === 'function';
5108
+ }
5109
+
5110
+ /**
5111
+ * Silence a Promise-like object.
5112
+ *
5113
+ * This is useful for avoiding non-harmful, but potentially confusing "uncaught
5114
+ * play promise" rejection error messages.
5115
+ *
5116
+ * @param {Object} value
5117
+ * An object that may or may not be `Promise`-like.
5118
+ */
5119
+ function silencePromise(value) {
5120
+ if (isPromise(value)) {
5121
+ value.then(null, function (e) {});
5122
+ }
5123
+ }
5124
+
5125
+ /**
5126
+ * @file text-track-list-converter.js Utilities for capturing text track state and
5127
+ * re-creating tracks based on a capture.
5128
+ *
5129
+ * @module text-track-list-converter
5130
+ */
5131
+
5132
+ /**
5133
+ * Examine a single {@link TextTrack} and return a JSON-compatible javascript object that
5134
+ * represents the {@link TextTrack}'s state.
5135
+ *
5136
+ * @param {TextTrack} track
5137
+ * The text track to query.
5138
+ *
5139
+ * @return {Object}
5140
+ * A serializable javascript representation of the TextTrack.
5141
+ * @private
5142
+ */
5143
+ var trackToJson_ = function trackToJson_(track) {
5144
+ var ret = ['kind', 'label', 'language', 'id', 'inBandMetadataTrackDispatchType', 'mode', 'src'].reduce(function (acc, prop, i) {
5145
+
5146
+ if (track[prop]) {
5147
+ acc[prop] = track[prop];
5148
+ }
5149
+
5150
+ return acc;
5151
+ }, {
5152
+ cues: track.cues && Array.prototype.map.call(track.cues, function (cue) {
5153
+ return {
5154
+ startTime: cue.startTime,
5155
+ endTime: cue.endTime,
5156
+ text: cue.text,
5157
+ id: cue.id
5158
+ };
5159
+ })
5160
+ });
5161
+
5162
+ return ret;
5163
+ };
5164
+
5165
+ /**
5166
+ * Examine a {@link Tech} and return a JSON-compatible javascript array that represents the
5167
+ * state of all {@link TextTrack}s currently configured. The return array is compatible with
5168
+ * {@link text-track-list-converter:jsonToTextTracks}.
5169
+ *
5170
+ * @param {Tech} tech
5171
+ * The tech object to query
5172
+ *
5173
+ * @return {Array}
5174
+ * A serializable javascript representation of the {@link Tech}s
5175
+ * {@link TextTrackList}.
5176
+ */
5177
+ var textTracksToJson = function textTracksToJson(tech) {
5178
+
5179
+ var trackEls = tech.$$('track');
5180
+
5181
+ var trackObjs = Array.prototype.map.call(trackEls, function (t) {
5182
+ return t.track;
5183
+ });
5184
+ var tracks = Array.prototype.map.call(trackEls, function (trackEl) {
5185
+ var json = trackToJson_(trackEl.track);
5186
+
5187
+ if (trackEl.src) {
5188
+ json.src = trackEl.src;
5189
+ }
5190
+ return json;
5191
+ });
5192
+
5193
+ return tracks.concat(Array.prototype.filter.call(tech.textTracks(), function (track) {
5194
+ return trackObjs.indexOf(track) === -1;
5195
+ }).map(trackToJson_));
5196
+ };
5197
+
5198
+ /**
5199
+ * Create a set of remote {@link TextTrack}s on a {@link Tech} based on an array of javascript
5200
+ * object {@link TextTrack} representations.
5201
+ *
5202
+ * @param {Array} json
5203
+ * An array of `TextTrack` representation objects, like those that would be
5204
+ * produced by `textTracksToJson`.
5205
+ *
5206
+ * @param {Tech} tech
5207
+ * The `Tech` to create the `TextTrack`s on.
5208
+ */
5209
+ var jsonToTextTracks = function jsonToTextTracks(json, tech) {
5210
+ json.forEach(function (track) {
5211
+ var addedTrack = tech.addRemoteTextTrack(track).track;
5212
+
5213
+ if (!track.src && track.cues) {
5214
+ track.cues.forEach(function (cue) {
5215
+ return addedTrack.addCue(cue);
5216
+ });
5217
+ }
5218
+ });
5219
+
5220
+ return tech.textTracks();
5221
+ };
5222
+
5223
+ var textTrackConverter = { textTracksToJson: textTracksToJson, jsonToTextTracks: jsonToTextTracks, trackToJson_: trackToJson_ };
5224
+
5225
+ /**
5226
+ * @file modal-dialog.js
5227
+ */
5228
+
5229
+ var MODAL_CLASS_NAME = 'vjs-modal-dialog';
5230
+ var ESC = 27;
5231
+
5232
+ /**
5233
+ * The `ModalDialog` displays over the video and its controls, which blocks
5234
+ * interaction with the player until it is closed.
5235
+ *
5236
+ * Modal dialogs include a "Close" button and will close when that button
5237
+ * is activated - or when ESC is pressed anywhere.
5238
+ *
5239
+ * @extends Component
5240
+ */
5241
+
5242
+ var ModalDialog = function (_Component) {
5243
+ inherits(ModalDialog, _Component);
5244
+
5245
+ /**
5246
+ * Create an instance of this class.
5247
+ *
5248
+ * @param {Player} player
5249
+ * The `Player` that this class should be attached to.
5250
+ *
5251
+ * @param {Object} [options]
5252
+ * The key/value store of player options.
5253
+ *
5254
+ * @param {Mixed} [options.content=undefined]
5255
+ * Provide customized content for this modal.
5256
+ *
5257
+ * @param {string} [options.description]
5258
+ * A text description for the modal, primarily for accessibility.
5259
+ *
5260
+ * @param {boolean} [options.fillAlways=false]
5261
+ * Normally, modals are automatically filled only the first time
5262
+ * they open. This tells the modal to refresh its content
5263
+ * every time it opens.
5264
+ *
5265
+ * @param {string} [options.label]
5266
+ * A text label for the modal, primarily for accessibility.
5267
+ *
5268
+ * @param {boolean} [options.temporary=true]
5269
+ * If `true`, the modal can only be opened once; it will be
5270
+ * disposed as soon as it's closed.
5271
+ *
5272
+ * @param {boolean} [options.uncloseable=false]
5273
+ * If `true`, the user will not be able to close the modal
5274
+ * through the UI in the normal ways. Programmatic closing is
5275
+ * still possible.
5276
+ */
5277
+ function ModalDialog(player, options) {
5278
+ classCallCheck(this, ModalDialog);
5279
+
5280
+ var _this = possibleConstructorReturn(this, _Component.call(this, player, options));
5281
+
5282
+ _this.opened_ = _this.hasBeenOpened_ = _this.hasBeenFilled_ = false;
5283
+
5284
+ _this.closeable(!_this.options_.uncloseable);
5285
+ _this.content(_this.options_.content);
5286
+
5287
+ // Make sure the contentEl is defined AFTER any children are initialized
5288
+ // because we only want the contents of the modal in the contentEl
5289
+ // (not the UI elements like the close button).
5290
+ _this.contentEl_ = createEl('div', {
5291
+ className: MODAL_CLASS_NAME + '-content'
5292
+ }, {
5293
+ role: 'document'
5294
+ });
5295
+
5296
+ _this.descEl_ = createEl('p', {
5297
+ className: MODAL_CLASS_NAME + '-description vjs-control-text',
5298
+ id: _this.el().getAttribute('aria-describedby')
5299
+ });
5300
+
5301
+ textContent(_this.descEl_, _this.description());
5302
+ _this.el_.appendChild(_this.descEl_);
5303
+ _this.el_.appendChild(_this.contentEl_);
5304
+ return _this;
5305
+ }
5306
+
5307
+ /**
5308
+ * Create the `ModalDialog`'s DOM element
5309
+ *
5310
+ * @return {Element}
5311
+ * The DOM element that gets created.
5312
+ */
5313
+
5314
+
5315
+ ModalDialog.prototype.createEl = function createEl$$1() {
5316
+ return _Component.prototype.createEl.call(this, 'div', {
5317
+ className: this.buildCSSClass(),
5318
+ tabIndex: -1
5319
+ }, {
5320
+ 'aria-describedby': this.id() + '_description',
5321
+ 'aria-hidden': 'true',
5322
+ 'aria-label': this.label(),
5323
+ 'role': 'dialog'
5324
+ });
5325
+ };
5326
+
5327
+ ModalDialog.prototype.dispose = function dispose() {
5328
+ this.contentEl_ = null;
5329
+ this.descEl_ = null;
5330
+ this.previouslyActiveEl_ = null;
5331
+
5332
+ _Component.prototype.dispose.call(this);
5333
+ };
5334
+
5335
+ /**
5336
+ * Builds the default DOM `className`.
5337
+ *
5338
+ * @return {string}
5339
+ * The DOM `className` for this object.
5340
+ */
5341
+
5342
+
5343
+ ModalDialog.prototype.buildCSSClass = function buildCSSClass() {
5344
+ return MODAL_CLASS_NAME + ' vjs-hidden ' + _Component.prototype.buildCSSClass.call(this);
5345
+ };
5346
+
5347
+ /**
5348
+ * Handles `keydown` events on the document, looking for ESC, which closes
5349
+ * the modal.
5350
+ *
5351
+ * @param {EventTarget~Event} e
5352
+ * The keypress that triggered this event.
5353
+ *
5354
+ * @listens keydown
5355
+ */
5356
+
5357
+
5358
+ ModalDialog.prototype.handleKeyPress = function handleKeyPress(e) {
5359
+ if (e.which === ESC && this.closeable()) {
5360
+ this.close();
5361
+ }
5362
+ };
5363
+
5364
+ /**
5365
+ * Returns the label string for this modal. Primarily used for accessibility.
5366
+ *
5367
+ * @return {string}
5368
+ * the localized or raw label of this modal.
5369
+ */
5370
+
5371
+
5372
+ ModalDialog.prototype.label = function label() {
5373
+ return this.localize(this.options_.label || 'Modal Window');
5374
+ };
5375
+
5376
+ /**
5377
+ * Returns the description string for this modal. Primarily used for
5378
+ * accessibility.
5379
+ *
5380
+ * @return {string}
5381
+ * The localized or raw description of this modal.
5382
+ */
5383
+
5384
+
5385
+ ModalDialog.prototype.description = function description() {
5386
+ var desc = this.options_.description || this.localize('This is a modal window.');
5387
+
5388
+ // Append a universal closeability message if the modal is closeable.
5389
+ if (this.closeable()) {
5390
+ desc += ' ' + this.localize('This modal can be closed by pressing the Escape key or activating the close button.');
5391
+ }
5392
+
5393
+ return desc;
5394
+ };
5395
+
5396
+ /**
5397
+ * Opens the modal.
5398
+ *
5399
+ * @fires ModalDialog#beforemodalopen
5400
+ * @fires ModalDialog#modalopen
5401
+ */
5402
+
5403
+
5404
+ ModalDialog.prototype.open = function open() {
5405
+ if (!this.opened_) {
5406
+ var player = this.player();
5407
+
5408
+ /**
5409
+ * Fired just before a `ModalDialog` is opened.
5410
+ *
5411
+ * @event ModalDialog#beforemodalopen
5412
+ * @type {EventTarget~Event}
5413
+ */
5414
+ this.trigger('beforemodalopen');
5415
+ this.opened_ = true;
5416
+
5417
+ // Fill content if the modal has never opened before and
5418
+ // never been filled.
5419
+ if (this.options_.fillAlways || !this.hasBeenOpened_ && !this.hasBeenFilled_) {
5420
+ this.fill();
5421
+ }
5422
+
5423
+ // If the player was playing, pause it and take note of its previously
5424
+ // playing state.
5425
+ this.wasPlaying_ = !player.paused();
5426
+
5427
+ if (this.options_.pauseOnOpen && this.wasPlaying_) {
5428
+ player.pause();
5429
+ }
5430
+
5431
+ if (this.closeable()) {
5432
+ this.on(this.el_.ownerDocument, 'keydown', bind(this, this.handleKeyPress));
5433
+ }
5434
+
5435
+ // Hide controls and note if they were enabled.
5436
+ this.hadControls_ = player.controls();
5437
+ player.controls(false);
5438
+
5439
+ this.show();
5440
+ this.conditionalFocus_();
5441
+ this.el().setAttribute('aria-hidden', 'false');
5442
+
5443
+ /**
5444
+ * Fired just after a `ModalDialog` is opened.
5445
+ *
5446
+ * @event ModalDialog#modalopen
5447
+ * @type {EventTarget~Event}
5448
+ */
5449
+ this.trigger('modalopen');
5450
+ this.hasBeenOpened_ = true;
5451
+ }
5452
+ };
5453
+
5454
+ /**
5455
+ * If the `ModalDialog` is currently open or closed.
5456
+ *
5457
+ * @param {boolean} [value]
5458
+ * If given, it will open (`true`) or close (`false`) the modal.
5459
+ *
5460
+ * @return {boolean}
5461
+ * the current open state of the modaldialog
5462
+ */
5463
+
5464
+
5465
+ ModalDialog.prototype.opened = function opened(value) {
5466
+ if (typeof value === 'boolean') {
5467
+ this[value ? 'open' : 'close']();
5468
+ }
5469
+ return this.opened_;
5470
+ };
5471
+
5472
+ /**
5473
+ * Closes the modal, does nothing if the `ModalDialog` is
5474
+ * not open.
5475
+ *
5476
+ * @fires ModalDialog#beforemodalclose
5477
+ * @fires ModalDialog#modalclose
5478
+ */
5479
+
5480
+
5481
+ ModalDialog.prototype.close = function close() {
5482
+ if (!this.opened_) {
5483
+ return;
5484
+ }
5485
+ var player = this.player();
5486
+
5487
+ /**
5488
+ * Fired just before a `ModalDialog` is closed.
5489
+ *
5490
+ * @event ModalDialog#beforemodalclose
5491
+ * @type {EventTarget~Event}
5492
+ */
5493
+ this.trigger('beforemodalclose');
5494
+ this.opened_ = false;
5495
+
5496
+ if (this.wasPlaying_ && this.options_.pauseOnOpen) {
5497
+ player.play();
5498
+ }
5499
+
5500
+ if (this.closeable()) {
5501
+ this.off(this.el_.ownerDocument, 'keydown', bind(this, this.handleKeyPress));
5502
+ }
5503
+
5504
+ if (this.hadControls_) {
5505
+ player.controls(true);
5506
+ }
5507
+
5508
+ this.hide();
5509
+ this.el().setAttribute('aria-hidden', 'true');
5510
+
5511
+ /**
5512
+ * Fired just after a `ModalDialog` is closed.
5513
+ *
5514
+ * @event ModalDialog#modalclose
5515
+ * @type {EventTarget~Event}
5516
+ */
5517
+ this.trigger('modalclose');
5518
+ this.conditionalBlur_();
5519
+
5520
+ if (this.options_.temporary) {
5521
+ this.dispose();
5522
+ }
5523
+ };
5524
+
5525
+ /**
5526
+ * Check to see if the `ModalDialog` is closeable via the UI.
5527
+ *
5528
+ * @param {boolean} [value]
5529
+ * If given as a boolean, it will set the `closeable` option.
5530
+ *
5531
+ * @return {boolean}
5532
+ * Returns the final value of the closable option.
5533
+ */
5534
+
5535
+
5536
+ ModalDialog.prototype.closeable = function closeable(value) {
5537
+ if (typeof value === 'boolean') {
5538
+ var closeable = this.closeable_ = !!value;
5539
+ var close = this.getChild('closeButton');
5540
+
5541
+ // If this is being made closeable and has no close button, add one.
5542
+ if (closeable && !close) {
5543
+
5544
+ // The close button should be a child of the modal - not its
5545
+ // content element, so temporarily change the content element.
5546
+ var temp = this.contentEl_;
5547
+
5548
+ this.contentEl_ = this.el_;
5549
+ close = this.addChild('closeButton', { controlText: 'Close Modal Dialog' });
5550
+ this.contentEl_ = temp;
5551
+ this.on(close, 'close', this.close);
5552
+ }
5553
+
5554
+ // If this is being made uncloseable and has a close button, remove it.
5555
+ if (!closeable && close) {
5556
+ this.off(close, 'close', this.close);
5557
+ this.removeChild(close);
5558
+ close.dispose();
5559
+ }
5560
+ }
5561
+ return this.closeable_;
5562
+ };
5563
+
5564
+ /**
5565
+ * Fill the modal's content element with the modal's "content" option.
5566
+ * The content element will be emptied before this change takes place.
5567
+ */
5568
+
5569
+
5570
+ ModalDialog.prototype.fill = function fill() {
5571
+ this.fillWith(this.content());
5572
+ };
5573
+
5574
+ /**
5575
+ * Fill the modal's content element with arbitrary content.
5576
+ * The content element will be emptied before this change takes place.
5577
+ *
5578
+ * @fires ModalDialog#beforemodalfill
5579
+ * @fires ModalDialog#modalfill
5580
+ *
5581
+ * @param {Mixed} [content]
5582
+ * The same rules apply to this as apply to the `content` option.
5583
+ */
5584
+
5585
+
5586
+ ModalDialog.prototype.fillWith = function fillWith(content) {
5587
+ var contentEl = this.contentEl();
5588
+ var parentEl = contentEl.parentNode;
5589
+ var nextSiblingEl = contentEl.nextSibling;
5590
+
5591
+ /**
5592
+ * Fired just before a `ModalDialog` is filled with content.
5593
+ *
5594
+ * @event ModalDialog#beforemodalfill
5595
+ * @type {EventTarget~Event}
5596
+ */
5597
+ this.trigger('beforemodalfill');
5598
+ this.hasBeenFilled_ = true;
5599
+
5600
+ // Detach the content element from the DOM before performing
5601
+ // manipulation to avoid modifying the live DOM multiple times.
5602
+ parentEl.removeChild(contentEl);
5603
+ this.empty();
5604
+ insertContent(contentEl, content);
5605
+ /**
5606
+ * Fired just after a `ModalDialog` is filled with content.
5607
+ *
5608
+ * @event ModalDialog#modalfill
5609
+ * @type {EventTarget~Event}
5610
+ */
5611
+ this.trigger('modalfill');
5612
+
5613
+ // Re-inject the re-filled content element.
5614
+ if (nextSiblingEl) {
5615
+ parentEl.insertBefore(contentEl, nextSiblingEl);
5616
+ } else {
5617
+ parentEl.appendChild(contentEl);
5618
+ }
5619
+
5620
+ // make sure that the close button is last in the dialog DOM
5621
+ var closeButton = this.getChild('closeButton');
5622
+
5623
+ if (closeButton) {
5624
+ parentEl.appendChild(closeButton.el_);
5625
+ }
5626
+ };
5627
+
5628
+ /**
5629
+ * Empties the content element. This happens anytime the modal is filled.
5630
+ *
5631
+ * @fires ModalDialog#beforemodalempty
5632
+ * @fires ModalDialog#modalempty
5633
+ */
5634
+
5635
+
5636
+ ModalDialog.prototype.empty = function empty() {
5637
+ /**
5638
+ * Fired just before a `ModalDialog` is emptied.
5639
+ *
5640
+ * @event ModalDialog#beforemodalempty
5641
+ * @type {EventTarget~Event}
5642
+ */
5643
+ this.trigger('beforemodalempty');
5644
+ emptyEl(this.contentEl());
5645
+
5646
+ /**
5647
+ * Fired just after a `ModalDialog` is emptied.
5648
+ *
5649
+ * @event ModalDialog#modalempty
5650
+ * @type {EventTarget~Event}
5651
+ */
5652
+ this.trigger('modalempty');
5653
+ };
5654
+
5655
+ /**
5656
+ * Gets or sets the modal content, which gets normalized before being
5657
+ * rendered into the DOM.
5658
+ *
5659
+ * This does not update the DOM or fill the modal, but it is called during
5660
+ * that process.
5661
+ *
5662
+ * @param {Mixed} [value]
5663
+ * If defined, sets the internal content value to be used on the
5664
+ * next call(s) to `fill`. This value is normalized before being
5665
+ * inserted. To "clear" the internal content value, pass `null`.
5666
+ *
5667
+ * @return {Mixed}
5668
+ * The current content of the modal dialog
5669
+ */
5670
+
5671
+
5672
+ ModalDialog.prototype.content = function content(value) {
5673
+ if (typeof value !== 'undefined') {
5674
+ this.content_ = value;
5675
+ }
5676
+ return this.content_;
5677
+ };
5678
+
5679
+ /**
5680
+ * conditionally focus the modal dialog if focus was previously on the player.
5681
+ *
5682
+ * @private
5683
+ */
5684
+
5685
+
5686
+ ModalDialog.prototype.conditionalFocus_ = function conditionalFocus_() {
5687
+ var activeEl = document_1.activeElement;
5688
+ var playerEl = this.player_.el_;
5689
+
5690
+ this.previouslyActiveEl_ = null;
5691
+
5692
+ if (playerEl.contains(activeEl) || playerEl === activeEl) {
5693
+ this.previouslyActiveEl_ = activeEl;
5694
+
5695
+ this.focus();
5696
+
5697
+ this.on(document_1, 'keydown', this.handleKeyDown);
5698
+ }
5699
+ };
5700
+
5701
+ /**
5702
+ * conditionally blur the element and refocus the last focused element
5703
+ *
5704
+ * @private
5705
+ */
5706
+
5707
+
5708
+ ModalDialog.prototype.conditionalBlur_ = function conditionalBlur_() {
5709
+ if (this.previouslyActiveEl_) {
5710
+ this.previouslyActiveEl_.focus();
5711
+ this.previouslyActiveEl_ = null;
5712
+ }
5713
+
5714
+ this.off(document_1, 'keydown', this.handleKeyDown);
5715
+ };
5716
+
5717
+ /**
5718
+ * Keydown handler. Attached when modal is focused.
5719
+ *
5720
+ * @listens keydown
5721
+ */
5722
+
5723
+
5724
+ ModalDialog.prototype.handleKeyDown = function handleKeyDown(event) {
5725
+ // exit early if it isn't a tab key
5726
+ if (event.which !== 9) {
5727
+ return;
5728
+ }
5729
+
5730
+ var focusableEls = this.focusableEls_();
5731
+ var activeEl = this.el_.querySelector(':focus');
5732
+ var focusIndex = void 0;
5733
+
5734
+ for (var i = 0; i < focusableEls.length; i++) {
5735
+ if (activeEl === focusableEls[i]) {
5736
+ focusIndex = i;
5737
+ break;
5738
+ }
5739
+ }
5740
+
5741
+ if (document_1.activeElement === this.el_) {
5742
+ focusIndex = 0;
5743
+ }
5744
+
5745
+ if (event.shiftKey && focusIndex === 0) {
5746
+ focusableEls[focusableEls.length - 1].focus();
5747
+ event.preventDefault();
5748
+ } else if (!event.shiftKey && focusIndex === focusableEls.length - 1) {
5749
+ focusableEls[0].focus();
5750
+ event.preventDefault();
5751
+ }
5752
+ };
5753
+
5754
+ /**
5755
+ * get all focusable elements
5756
+ *
5757
+ * @private
5758
+ */
5759
+
5760
+
5761
+ ModalDialog.prototype.focusableEls_ = function focusableEls_() {
5762
+ var allChildren = this.el_.querySelectorAll('*');
5763
+
5764
+ return Array.prototype.filter.call(allChildren, function (child) {
5765
+ return (child instanceof window_1.HTMLAnchorElement || child instanceof window_1.HTMLAreaElement) && child.hasAttribute('href') || (child instanceof window_1.HTMLInputElement || child instanceof window_1.HTMLSelectElement || child instanceof window_1.HTMLTextAreaElement || child instanceof window_1.HTMLButtonElement) && !child.hasAttribute('disabled') || child instanceof window_1.HTMLIFrameElement || child instanceof window_1.HTMLObjectElement || child instanceof window_1.HTMLEmbedElement || child.hasAttribute('tabindex') && child.getAttribute('tabindex') !== -1 || child.hasAttribute('contenteditable');
5766
+ });
5767
+ };
5768
+
5769
+ return ModalDialog;
5770
+ }(Component);
5771
+
5772
+ /**
5773
+ * Default options for `ModalDialog` default options.
5774
+ *
5775
+ * @type {Object}
5776
+ * @private
5777
+ */
5778
+
5779
+
5780
+ ModalDialog.prototype.options_ = {
5781
+ pauseOnOpen: true,
5782
+ temporary: true
5783
+ };
5784
+
5785
+ Component.registerComponent('ModalDialog', ModalDialog);
5786
+
5787
+ /**
5788
+ * @file track-list.js
5789
+ */
5790
+
5791
+ /**
5792
+ * Common functionaliy between {@link TextTrackList}, {@link AudioTrackList}, and
5793
+ * {@link VideoTrackList}
5794
+ *
5795
+ * @extends EventTarget
5796
+ */
5797
+
5798
+ var TrackList = function (_EventTarget) {
5799
+ inherits(TrackList, _EventTarget);
5800
+
5801
+ /**
5802
+ * Create an instance of this class
5803
+ *
5804
+ * @param {Track[]} tracks
5805
+ * A list of tracks to initialize the list with.
5806
+ *
5807
+ * @abstract
5808
+ */
5809
+ function TrackList() {
5810
+ var tracks = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
5811
+ classCallCheck(this, TrackList);
5812
+
5813
+ var _this = possibleConstructorReturn(this, _EventTarget.call(this));
5814
+
5815
+ _this.tracks_ = [];
5816
+
5817
+ /**
5818
+ * @memberof TrackList
5819
+ * @member {number} length
5820
+ * The current number of `Track`s in the this Trackist.
5821
+ * @instance
5822
+ */
5823
+ Object.defineProperty(_this, 'length', {
5824
+ get: function get$$1() {
5825
+ return this.tracks_.length;
5826
+ }
5827
+ });
5828
+
5829
+ for (var i = 0; i < tracks.length; i++) {
5830
+ _this.addTrack(tracks[i]);
5831
+ }
5832
+ return _this;
5833
+ }
5834
+
5835
+ /**
5836
+ * Add a {@link Track} to the `TrackList`
5837
+ *
5838
+ * @param {Track} track
5839
+ * The audio, video, or text track to add to the list.
5840
+ *
5841
+ * @fires TrackList#addtrack
5842
+ */
5843
+
5844
+
5845
+ TrackList.prototype.addTrack = function addTrack(track) {
5846
+ var index = this.tracks_.length;
5847
+
5848
+ if (!('' + index in this)) {
5849
+ Object.defineProperty(this, index, {
5850
+ get: function get$$1() {
5851
+ return this.tracks_[index];
5852
+ }
5853
+ });
5854
+ }
5855
+
5856
+ // Do not add duplicate tracks
5857
+ if (this.tracks_.indexOf(track) === -1) {
5858
+ this.tracks_.push(track);
5859
+ /**
5860
+ * Triggered when a track is added to a track list.
5861
+ *
5862
+ * @event TrackList#addtrack
5863
+ * @type {EventTarget~Event}
5864
+ * @property {Track} track
5865
+ * A reference to track that was added.
5866
+ */
5867
+ this.trigger({
5868
+ track: track,
5869
+ type: 'addtrack'
5870
+ });
5871
+ }
5872
+ };
5873
+
5874
+ /**
5875
+ * Remove a {@link Track} from the `TrackList`
5876
+ *
5877
+ * @param {Track} rtrack
5878
+ * The audio, video, or text track to remove from the list.
5879
+ *
5880
+ * @fires TrackList#removetrack
5881
+ */
5882
+
5883
+
5884
+ TrackList.prototype.removeTrack = function removeTrack(rtrack) {
5885
+ var track = void 0;
5886
+
5887
+ for (var i = 0, l = this.length; i < l; i++) {
5888
+ if (this[i] === rtrack) {
5889
+ track = this[i];
5890
+ if (track.off) {
5891
+ track.off();
5892
+ }
5893
+
5894
+ this.tracks_.splice(i, 1);
5895
+
5896
+ break;
5897
+ }
5898
+ }
5899
+
5900
+ if (!track) {
5901
+ return;
5902
+ }
5903
+
5904
+ /**
5905
+ * Triggered when a track is removed from track list.
5906
+ *
5907
+ * @event TrackList#removetrack
5908
+ * @type {EventTarget~Event}
5909
+ * @property {Track} track
5910
+ * A reference to track that was removed.
5911
+ */
5912
+ this.trigger({
5913
+ track: track,
5914
+ type: 'removetrack'
5915
+ });
5916
+ };
5917
+
5918
+ /**
5919
+ * Get a Track from the TrackList by a tracks id
5920
+ *
5921
+ * @param {String} id - the id of the track to get
5922
+ * @method getTrackById
5923
+ * @return {Track}
5924
+ * @private
5925
+ */
5926
+
5927
+
5928
+ TrackList.prototype.getTrackById = function getTrackById(id) {
5929
+ var result = null;
5930
+
5931
+ for (var i = 0, l = this.length; i < l; i++) {
5932
+ var track = this[i];
5933
+
5934
+ if (track.id === id) {
5935
+ result = track;
5936
+ break;
5937
+ }
5938
+ }
5939
+
5940
+ return result;
5941
+ };
5942
+
5943
+ return TrackList;
5944
+ }(EventTarget);
5945
+
5946
+ /**
5947
+ * Triggered when a different track is selected/enabled.
5948
+ *
5949
+ * @event TrackList#change
5950
+ * @type {EventTarget~Event}
5951
+ */
5952
+
5953
+ /**
5954
+ * Events that can be called with on + eventName. See {@link EventHandler}.
5955
+ *
5956
+ * @property {Object} TrackList#allowedEvents_
5957
+ * @private
5958
+ */
5959
+
5960
+
5961
+ TrackList.prototype.allowedEvents_ = {
5962
+ change: 'change',
5963
+ addtrack: 'addtrack',
5964
+ removetrack: 'removetrack'
5965
+ };
5966
+
5967
+ // emulate attribute EventHandler support to allow for feature detection
5968
+ for (var event in TrackList.prototype.allowedEvents_) {
5969
+ TrackList.prototype['on' + event] = null;
5970
+ }
5971
+
5972
+ /**
5973
+ * @file audio-track-list.js
5974
+ */
5975
+
5976
+ /**
5977
+ * Anywhere we call this function we diverge from the spec
5978
+ * as we only support one enabled audiotrack at a time
5979
+ *
5980
+ * @param {AudioTrackList} list
5981
+ * list to work on
5982
+ *
5983
+ * @param {AudioTrack} track
5984
+ * The track to skip
5985
+ *
5986
+ * @private
5987
+ */
5988
+ var disableOthers = function disableOthers(list, track) {
5989
+ for (var i = 0; i < list.length; i++) {
5990
+ if (!Object.keys(list[i]).length || track.id === list[i].id) {
5991
+ continue;
5992
+ }
5993
+ // another audio track is enabled, disable it
5994
+ list[i].enabled = false;
5995
+ }
5996
+ };
5997
+
5998
+ /**
5999
+ * The current list of {@link AudioTrack} for a media file.
6000
+ *
6001
+ * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#audiotracklist}
6002
+ * @extends TrackList
6003
+ */
6004
+
6005
+ var AudioTrackList = function (_TrackList) {
6006
+ inherits(AudioTrackList, _TrackList);
6007
+
6008
+ /**
6009
+ * Create an instance of this class.
6010
+ *
6011
+ * @param {AudioTrack[]} [tracks=[]]
6012
+ * A list of `AudioTrack` to instantiate the list with.
6013
+ */
6014
+ function AudioTrackList() {
6015
+ var tracks = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
6016
+ classCallCheck(this, AudioTrackList);
6017
+
6018
+ // make sure only 1 track is enabled
6019
+ // sorted from last index to first index
6020
+ for (var i = tracks.length - 1; i >= 0; i--) {
6021
+ if (tracks[i].enabled) {
6022
+ disableOthers(tracks, tracks[i]);
6023
+ break;
6024
+ }
6025
+ }
6026
+
6027
+ var _this = possibleConstructorReturn(this, _TrackList.call(this, tracks));
6028
+
6029
+ _this.changing_ = false;
6030
+ return _this;
6031
+ }
6032
+
6033
+ /**
6034
+ * Add an {@link AudioTrack} to the `AudioTrackList`.
6035
+ *
6036
+ * @param {AudioTrack} track
6037
+ * The AudioTrack to add to the list
6038
+ *
6039
+ * @fires TrackList#addtrack
6040
+ */
6041
+
6042
+
6043
+ AudioTrackList.prototype.addTrack = function addTrack(track) {
6044
+ var _this2 = this;
6045
+
6046
+ if (track.enabled) {
6047
+ disableOthers(this, track);
6048
+ }
6049
+
6050
+ _TrackList.prototype.addTrack.call(this, track);
6051
+ // native tracks don't have this
6052
+ if (!track.addEventListener) {
6053
+ return;
6054
+ }
6055
+
6056
+ /**
6057
+ * @listens AudioTrack#enabledchange
6058
+ * @fires TrackList#change
6059
+ */
6060
+ track.addEventListener('enabledchange', function () {
6061
+ // when we are disabling other tracks (since we don't support
6062
+ // more than one track at a time) we will set changing_
6063
+ // to true so that we don't trigger additional change events
6064
+ if (_this2.changing_) {
6065
+ return;
6066
+ }
6067
+ _this2.changing_ = true;
6068
+ disableOthers(_this2, track);
6069
+ _this2.changing_ = false;
6070
+ _this2.trigger('change');
6071
+ });
6072
+ };
6073
+
6074
+ return AudioTrackList;
6075
+ }(TrackList);
6076
+
6077
+ /**
6078
+ * @file video-track-list.js
6079
+ */
6080
+
6081
+ /**
6082
+ * Un-select all other {@link VideoTrack}s that are selected.
6083
+ *
6084
+ * @param {VideoTrackList} list
6085
+ * list to work on
6086
+ *
6087
+ * @param {VideoTrack} track
6088
+ * The track to skip
6089
+ *
6090
+ * @private
6091
+ */
6092
+ var disableOthers$1 = function disableOthers(list, track) {
6093
+ for (var i = 0; i < list.length; i++) {
6094
+ if (!Object.keys(list[i]).length || track.id === list[i].id) {
6095
+ continue;
6096
+ }
6097
+ // another video track is enabled, disable it
6098
+ list[i].selected = false;
6099
+ }
6100
+ };
6101
+
6102
+ /**
6103
+ * The current list of {@link VideoTrack} for a video.
6104
+ *
6105
+ * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#videotracklist}
6106
+ * @extends TrackList
6107
+ */
6108
+
6109
+ var VideoTrackList = function (_TrackList) {
6110
+ inherits(VideoTrackList, _TrackList);
6111
+
6112
+ /**
6113
+ * Create an instance of this class.
6114
+ *
6115
+ * @param {VideoTrack[]} [tracks=[]]
6116
+ * A list of `VideoTrack` to instantiate the list with.
6117
+ */
6118
+ function VideoTrackList() {
6119
+ var tracks = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
6120
+ classCallCheck(this, VideoTrackList);
6121
+
6122
+ // make sure only 1 track is enabled
6123
+ // sorted from last index to first index
6124
+ for (var i = tracks.length - 1; i >= 0; i--) {
6125
+ if (tracks[i].selected) {
6126
+ disableOthers$1(tracks, tracks[i]);
6127
+ break;
6128
+ }
6129
+ }
6130
+
6131
+ var _this = possibleConstructorReturn(this, _TrackList.call(this, tracks));
6132
+
6133
+ _this.changing_ = false;
6134
+
6135
+ /**
6136
+ * @member {number} VideoTrackList#selectedIndex
6137
+ * The current index of the selected {@link VideoTrack`}.
6138
+ */
6139
+ Object.defineProperty(_this, 'selectedIndex', {
6140
+ get: function get$$1() {
6141
+ for (var _i = 0; _i < this.length; _i++) {
6142
+ if (this[_i].selected) {
6143
+ return _i;
6144
+ }
6145
+ }
6146
+ return -1;
6147
+ },
6148
+ set: function set$$1() {}
6149
+ });
6150
+ return _this;
6151
+ }
6152
+
6153
+ /**
6154
+ * Add a {@link VideoTrack} to the `VideoTrackList`.
6155
+ *
6156
+ * @param {VideoTrack} track
6157
+ * The VideoTrack to add to the list
6158
+ *
6159
+ * @fires TrackList#addtrack
6160
+ */
6161
+
6162
+
6163
+ VideoTrackList.prototype.addTrack = function addTrack(track) {
6164
+ var _this2 = this;
6165
+
6166
+ if (track.selected) {
6167
+ disableOthers$1(this, track);
6168
+ }
6169
+
6170
+ _TrackList.prototype.addTrack.call(this, track);
6171
+ // native tracks don't have this
6172
+ if (!track.addEventListener) {
6173
+ return;
6174
+ }
6175
+
6176
+ /**
6177
+ * @listens VideoTrack#selectedchange
6178
+ * @fires TrackList#change
6179
+ */
6180
+ track.addEventListener('selectedchange', function () {
6181
+ if (_this2.changing_) {
6182
+ return;
6183
+ }
6184
+ _this2.changing_ = true;
6185
+ disableOthers$1(_this2, track);
6186
+ _this2.changing_ = false;
6187
+ _this2.trigger('change');
6188
+ });
6189
+ };
6190
+
6191
+ return VideoTrackList;
6192
+ }(TrackList);
6193
+
6194
+ /**
6195
+ * @file text-track-list.js
6196
+ */
6197
+
6198
+ /**
6199
+ * The current list of {@link TextTrack} for a media file.
6200
+ *
6201
+ * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#texttracklist}
6202
+ * @extends TrackList
6203
+ */
6204
+
6205
+ var TextTrackList = function (_TrackList) {
6206
+ inherits(TextTrackList, _TrackList);
6207
+
6208
+ function TextTrackList() {
6209
+ classCallCheck(this, TextTrackList);
6210
+ return possibleConstructorReturn(this, _TrackList.apply(this, arguments));
6211
+ }
6212
+
6213
+ /**
6214
+ * Add a {@link TextTrack} to the `TextTrackList`
6215
+ *
6216
+ * @param {TextTrack} track
6217
+ * The text track to add to the list.
6218
+ *
6219
+ * @fires TrackList#addtrack
6220
+ */
6221
+ TextTrackList.prototype.addTrack = function addTrack(track) {
6222
+ _TrackList.prototype.addTrack.call(this, track);
6223
+
6224
+ /**
6225
+ * @listens TextTrack#modechange
6226
+ * @fires TrackList#change
6227
+ */
6228
+ track.addEventListener('modechange', bind(this, function () {
6229
+ this.trigger('change');
6230
+ }));
6231
+
6232
+ var nonLanguageTextTrackKind = ['metadata', 'chapters'];
6233
+
6234
+ if (nonLanguageTextTrackKind.indexOf(track.kind) === -1) {
6235
+ track.addEventListener('modechange', bind(this, function () {
6236
+ this.trigger('selectedlanguagechange');
6237
+ }));
6238
+ }
6239
+ };
6240
+
6241
+ return TextTrackList;
6242
+ }(TrackList);
6243
+
6244
+ /**
6245
+ * @file html-track-element-list.js
6246
+ */
6247
+
6248
+ /**
6249
+ * The current list of {@link HtmlTrackElement}s.
6250
+ */
6251
+ var HtmlTrackElementList = function () {
6252
+
6253
+ /**
6254
+ * Create an instance of this class.
6255
+ *
6256
+ * @param {HtmlTrackElement[]} [tracks=[]]
6257
+ * A list of `HtmlTrackElement` to instantiate the list with.
6258
+ */
6259
+ function HtmlTrackElementList() {
6260
+ var trackElements = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
6261
+ classCallCheck(this, HtmlTrackElementList);
6262
+
6263
+ this.trackElements_ = [];
6264
+
6265
+ /**
6266
+ * @memberof HtmlTrackElementList
6267
+ * @member {number} length
6268
+ * The current number of `Track`s in the this Trackist.
6269
+ * @instance
6270
+ */
6271
+ Object.defineProperty(this, 'length', {
6272
+ get: function get$$1() {
6273
+ return this.trackElements_.length;
6274
+ }
6275
+ });
6276
+
6277
+ for (var i = 0, length = trackElements.length; i < length; i++) {
6278
+ this.addTrackElement_(trackElements[i]);
6279
+ }
6280
+ }
6281
+
6282
+ /**
6283
+ * Add an {@link HtmlTrackElement} to the `HtmlTrackElementList`
6284
+ *
6285
+ * @param {HtmlTrackElement} trackElement
6286
+ * The track element to add to the list.
6287
+ *
6288
+ * @private
6289
+ */
6290
+
6291
+
6292
+ HtmlTrackElementList.prototype.addTrackElement_ = function addTrackElement_(trackElement) {
6293
+ var index = this.trackElements_.length;
6294
+
6295
+ if (!('' + index in this)) {
6296
+ Object.defineProperty(this, index, {
6297
+ get: function get$$1() {
6298
+ return this.trackElements_[index];
6299
+ }
6300
+ });
6301
+ }
6302
+
6303
+ // Do not add duplicate elements
6304
+ if (this.trackElements_.indexOf(trackElement) === -1) {
6305
+ this.trackElements_.push(trackElement);
6306
+ }
6307
+ };
6308
+
6309
+ /**
6310
+ * Get an {@link HtmlTrackElement} from the `HtmlTrackElementList` given an
6311
+ * {@link TextTrack}.
6312
+ *
6313
+ * @param {TextTrack} track
6314
+ * The track associated with a track element.
6315
+ *
6316
+ * @return {HtmlTrackElement|undefined}
6317
+ * The track element that was found or undefined.
6318
+ *
6319
+ * @private
6320
+ */
6321
+
6322
+
6323
+ HtmlTrackElementList.prototype.getTrackElementByTrack_ = function getTrackElementByTrack_(track) {
6324
+ var trackElement_ = void 0;
6325
+
6326
+ for (var i = 0, length = this.trackElements_.length; i < length; i++) {
6327
+ if (track === this.trackElements_[i].track) {
6328
+ trackElement_ = this.trackElements_[i];
6329
+
6330
+ break;
6331
+ }
6332
+ }
6333
+
6334
+ return trackElement_;
6335
+ };
6336
+
6337
+ /**
6338
+ * Remove a {@link HtmlTrackElement} from the `HtmlTrackElementList`
6339
+ *
6340
+ * @param {HtmlTrackElement} trackElement
6341
+ * The track element to remove from the list.
6342
+ *
6343
+ * @private
6344
+ */
6345
+
6346
+
6347
+ HtmlTrackElementList.prototype.removeTrackElement_ = function removeTrackElement_(trackElement) {
6348
+ for (var i = 0, length = this.trackElements_.length; i < length; i++) {
6349
+ if (trackElement === this.trackElements_[i]) {
6350
+ this.trackElements_.splice(i, 1);
6351
+
6352
+ break;
6353
+ }
6354
+ }
6355
+ };
6356
+
6357
+ return HtmlTrackElementList;
6358
+ }();
6359
+
6360
+ /**
6361
+ * @file text-track-cue-list.js
6362
+ */
6363
+
6364
+ /**
6365
+ * @typedef {Object} TextTrackCueList~TextTrackCue
6366
+ *
6367
+ * @property {string} id
6368
+ * The unique id for this text track cue
6369
+ *
6370
+ * @property {number} startTime
6371
+ * The start time for this text track cue
6372
+ *
6373
+ * @property {number} endTime
6374
+ * The end time for this text track cue
6375
+ *
6376
+ * @property {boolean} pauseOnExit
6377
+ * Pause when the end time is reached if true.
6378
+ *
6379
+ * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#texttrackcue}
6380
+ */
6381
+
6382
+ /**
6383
+ * A List of TextTrackCues.
6384
+ *
6385
+ * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#texttrackcuelist}
6386
+ */
6387
+ var TextTrackCueList = function () {
6388
+
6389
+ /**
6390
+ * Create an instance of this class..
6391
+ *
6392
+ * @param {Array} cues
6393
+ * A list of cues to be initialized with
6394
+ */
6395
+ function TextTrackCueList(cues) {
6396
+ classCallCheck(this, TextTrackCueList);
6397
+
6398
+ TextTrackCueList.prototype.setCues_.call(this, cues);
6399
+
6400
+ /**
6401
+ * @memberof TextTrackCueList
6402
+ * @member {number} length
6403
+ * The current number of `TextTrackCue`s in the TextTrackCueList.
6404
+ * @instance
6405
+ */
6406
+ Object.defineProperty(this, 'length', {
6407
+ get: function get$$1() {
6408
+ return this.length_;
6409
+ }
6410
+ });
6411
+ }
6412
+
6413
+ /**
6414
+ * A setter for cues in this list. Creates getters
6415
+ * an an index for the cues.
6416
+ *
6417
+ * @param {Array} cues
6418
+ * An array of cues to set
6419
+ *
6420
+ * @private
6421
+ */
6422
+
6423
+
6424
+ TextTrackCueList.prototype.setCues_ = function setCues_(cues) {
6425
+ var oldLength = this.length || 0;
6426
+ var i = 0;
6427
+ var l = cues.length;
6428
+
6429
+ this.cues_ = cues;
6430
+ this.length_ = cues.length;
6431
+
6432
+ var defineProp = function defineProp(index) {
6433
+ if (!('' + index in this)) {
6434
+ Object.defineProperty(this, '' + index, {
6435
+ get: function get$$1() {
6436
+ return this.cues_[index];
6437
+ }
6438
+ });
6439
+ }
6440
+ };
6441
+
6442
+ if (oldLength < l) {
6443
+ i = oldLength;
6444
+
6445
+ for (; i < l; i++) {
6446
+ defineProp.call(this, i);
6447
+ }
6448
+ }
6449
+ };
6450
+
6451
+ /**
6452
+ * Get a `TextTrackCue` that is currently in the `TextTrackCueList` by id.
6453
+ *
6454
+ * @param {string} id
6455
+ * The id of the cue that should be searched for.
6456
+ *
6457
+ * @return {TextTrackCueList~TextTrackCue|null}
6458
+ * A single cue or null if none was found.
6459
+ */
6460
+
6461
+
6462
+ TextTrackCueList.prototype.getCueById = function getCueById(id) {
6463
+ var result = null;
6464
+
6465
+ for (var i = 0, l = this.length; i < l; i++) {
6466
+ var cue = this[i];
6467
+
6468
+ if (cue.id === id) {
6469
+ result = cue;
6470
+ break;
6471
+ }
6472
+ }
6473
+
6474
+ return result;
6475
+ };
6476
+
6477
+ return TextTrackCueList;
6478
+ }();
6479
+
6480
+ /**
6481
+ * @file track-kinds.js
6482
+ */
6483
+
6484
+ /**
6485
+ * All possible `VideoTrackKind`s
6486
+ *
6487
+ * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-videotrack-kind
6488
+ * @typedef VideoTrack~Kind
6489
+ * @enum
6490
+ */
6491
+ var VideoTrackKind = {
6492
+ alternative: 'alternative',
6493
+ captions: 'captions',
6494
+ main: 'main',
6495
+ sign: 'sign',
6496
+ subtitles: 'subtitles',
6497
+ commentary: 'commentary'
6498
+ };
6499
+
6500
+ /**
6501
+ * All possible `AudioTrackKind`s
6502
+ *
6503
+ * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-audiotrack-kind
6504
+ * @typedef AudioTrack~Kind
6505
+ * @enum
6506
+ */
6507
+ var AudioTrackKind = {
6508
+ 'alternative': 'alternative',
6509
+ 'descriptions': 'descriptions',
6510
+ 'main': 'main',
6511
+ 'main-desc': 'main-desc',
6512
+ 'translation': 'translation',
6513
+ 'commentary': 'commentary'
6514
+ };
6515
+
6516
+ /**
6517
+ * All possible `TextTrackKind`s
6518
+ *
6519
+ * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-texttrack-kind
6520
+ * @typedef TextTrack~Kind
6521
+ * @enum
6522
+ */
6523
+ var TextTrackKind = {
6524
+ subtitles: 'subtitles',
6525
+ captions: 'captions',
6526
+ descriptions: 'descriptions',
6527
+ chapters: 'chapters',
6528
+ metadata: 'metadata'
6529
+ };
6530
+
6531
+ /**
6532
+ * All possible `TextTrackMode`s
6533
+ *
6534
+ * @see https://html.spec.whatwg.org/multipage/embedded-content.html#texttrackmode
6535
+ * @typedef TextTrack~Mode
6536
+ * @enum
6537
+ */
6538
+ var TextTrackMode = {
6539
+ disabled: 'disabled',
6540
+ hidden: 'hidden',
6541
+ showing: 'showing'
6542
+ };
6543
+
6544
+ /**
6545
+ * @file track.js
6546
+ */
6547
+
6548
+ /**
6549
+ * A Track class that contains all of the common functionality for {@link AudioTrack},
6550
+ * {@link VideoTrack}, and {@link TextTrack}.
6551
+ *
6552
+ * > Note: This class should not be used directly
6553
+ *
6554
+ * @see {@link https://html.spec.whatwg.org/multipage/embedded-content.html}
6555
+ * @extends EventTarget
6556
+ * @abstract
6557
+ */
6558
+
6559
+ var Track = function (_EventTarget) {
6560
+ inherits(Track, _EventTarget);
6561
+
6562
+ /**
6563
+ * Create an instance of this class.
6564
+ *
6565
+ * @param {Object} [options={}]
6566
+ * Object of option names and values
6567
+ *
6568
+ * @param {string} [options.kind='']
6569
+ * A valid kind for the track type you are creating.
6570
+ *
6571
+ * @param {string} [options.id='vjs_track_' + Guid.newGUID()]
6572
+ * A unique id for this AudioTrack.
6573
+ *
6574
+ * @param {string} [options.label='']
6575
+ * The menu label for this track.
6576
+ *
6577
+ * @param {string} [options.language='']
6578
+ * A valid two character language code.
6579
+ *
6580
+ * @abstract
6581
+ */
6582
+ function Track() {
6583
+ var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
6584
+ classCallCheck(this, Track);
6585
+
6586
+ var _this = possibleConstructorReturn(this, _EventTarget.call(this));
6587
+
6588
+ var trackProps = {
6589
+ id: options.id || 'vjs_track_' + newGUID(),
6590
+ kind: options.kind || '',
6591
+ label: options.label || '',
6592
+ language: options.language || ''
6593
+ };
6594
+
6595
+ /**
6596
+ * @memberof Track
6597
+ * @member {string} id
6598
+ * The id of this track. Cannot be changed after creation.
6599
+ * @instance
6600
+ *
6601
+ * @readonly
6602
+ */
6603
+
6604
+ /**
6605
+ * @memberof Track
6606
+ * @member {string} kind
6607
+ * The kind of track that this is. Cannot be changed after creation.
6608
+ * @instance
6609
+ *
6610
+ * @readonly
6611
+ */
6612
+
6613
+ /**
6614
+ * @memberof Track
6615
+ * @member {string} label
6616
+ * The label of this track. Cannot be changed after creation.
6617
+ * @instance
6618
+ *
6619
+ * @readonly
6620
+ */
6621
+
6622
+ /**
6623
+ * @memberof Track
6624
+ * @member {string} language
6625
+ * The two letter language code for this track. Cannot be changed after
6626
+ * creation.
6627
+ * @instance
6628
+ *
6629
+ * @readonly
6630
+ */
6631
+
6632
+ var _loop = function _loop(key) {
6633
+ Object.defineProperty(_this, key, {
6634
+ get: function get$$1() {
6635
+ return trackProps[key];
6636
+ },
6637
+ set: function set$$1() {}
6638
+ });
6639
+ };
6640
+
6641
+ for (var key in trackProps) {
6642
+ _loop(key);
6643
+ }
6644
+ return _this;
6645
+ }
6646
+
6647
+ return Track;
6648
+ }(EventTarget);
6649
+
6650
+ /**
6651
+ * @file url.js
6652
+ * @module url
6653
+ */
6654
+
6655
+ /**
6656
+ * @typedef {Object} url:URLObject
6657
+ *
6658
+ * @property {string} protocol
6659
+ * The protocol of the url that was parsed.
6660
+ *
6661
+ * @property {string} hostname
6662
+ * The hostname of the url that was parsed.
6663
+ *
6664
+ * @property {string} port
6665
+ * The port of the url that was parsed.
6666
+ *
6667
+ * @property {string} pathname
6668
+ * The pathname of the url that was parsed.
6669
+ *
6670
+ * @property {string} search
6671
+ * The search query of the url that was parsed.
6672
+ *
6673
+ * @property {string} hash
6674
+ * The hash of the url that was parsed.
6675
+ *
6676
+ * @property {string} host
6677
+ * The host of the url that was parsed.
6678
+ */
6679
+
6680
+ /**
6681
+ * Resolve and parse the elements of a URL.
6682
+ *
6683
+ * @param {String} url
6684
+ * The url to parse
6685
+ *
6686
+ * @return {url:URLObject}
6687
+ * An object of url details
6688
+ */
6689
+ var parseUrl = function parseUrl(url) {
6690
+ var props = ['protocol', 'hostname', 'port', 'pathname', 'search', 'hash', 'host'];
6691
+
6692
+ // add the url to an anchor and let the browser parse the URL
6693
+ var a = document_1.createElement('a');
6694
+
6695
+ a.href = url;
6696
+
6697
+ // IE8 (and 9?) Fix
6698
+ // ie8 doesn't parse the URL correctly until the anchor is actually
6699
+ // added to the body, and an innerHTML is needed to trigger the parsing
6700
+ var addToBody = a.host === '' && a.protocol !== 'file:';
6701
+ var div = void 0;
6702
+
6703
+ if (addToBody) {
6704
+ div = document_1.createElement('div');
6705
+ div.innerHTML = '<a href="' + url + '"></a>';
6706
+ a = div.firstChild;
6707
+ // prevent the div from affecting layout
6708
+ div.setAttribute('style', 'display:none; position:absolute;');
6709
+ document_1.body.appendChild(div);
6710
+ }
6711
+
6712
+ // Copy the specific URL properties to a new object
6713
+ // This is also needed for IE8 because the anchor loses its
6714
+ // properties when it's removed from the dom
6715
+ var details = {};
6716
+
6717
+ for (var i = 0; i < props.length; i++) {
6718
+ details[props[i]] = a[props[i]];
6719
+ }
6720
+
6721
+ // IE9 adds the port to the host property unlike everyone else. If
6722
+ // a port identifier is added for standard ports, strip it.
6723
+ if (details.protocol === 'http:') {
6724
+ details.host = details.host.replace(/:80$/, '');
6725
+ }
6726
+
6727
+ if (details.protocol === 'https:') {
6728
+ details.host = details.host.replace(/:443$/, '');
6729
+ }
6730
+
6731
+ if (!details.protocol) {
6732
+ details.protocol = window_1.location.protocol;
6733
+ }
6734
+
6735
+ if (addToBody) {
6736
+ document_1.body.removeChild(div);
6737
+ }
6738
+
6739
+ return details;
6740
+ };
6741
+
6742
+ /**
6743
+ * Get absolute version of relative URL. Used to tell flash correct URL.
6744
+ *
6745
+ *
6746
+ * @param {string} url
6747
+ * URL to make absolute
6748
+ *
6749
+ * @return {string}
6750
+ * Absolute URL
6751
+ *
6752
+ * @see http://stackoverflow.com/questions/470832/getting-an-absolute-url-from-a-relative-one-ie6-issue
6753
+ */
6754
+ var getAbsoluteURL = function getAbsoluteURL(url) {
6755
+ // Check if absolute URL
6756
+ if (!url.match(/^https?:\/\//)) {
6757
+ // Convert to absolute URL. Flash hosted off-site needs an absolute URL.
6758
+ var div = document_1.createElement('div');
6759
+
6760
+ div.innerHTML = '<a href="' + url + '">x</a>';
6761
+ url = div.firstChild.href;
6762
+ }
6763
+
6764
+ return url;
6765
+ };
6766
+
6767
+ /**
6768
+ * Returns the extension of the passed file name. It will return an empty string
6769
+ * if passed an invalid path.
6770
+ *
6771
+ * @param {string} path
6772
+ * The fileName path like '/path/to/file.mp4'
6773
+ *
6774
+ * @returns {string}
6775
+ * The extension in lower case or an empty string if no
6776
+ * extension could be found.
6777
+ */
6778
+ var getFileExtension = function getFileExtension(path) {
6779
+ if (typeof path === 'string') {
6780
+ var splitPathRe = /^(\/?)([\s\S]*?)((?:\.{1,2}|[^\/]+?)(\.([^\.\/\?]+)))(?:[\/]*|[\?].*)$/i;
6781
+ var pathParts = splitPathRe.exec(path);
6782
+
6783
+ if (pathParts) {
6784
+ return pathParts.pop().toLowerCase();
6785
+ }
6786
+ }
6787
+
6788
+ return '';
6789
+ };
6790
+
6791
+ /**
6792
+ * Returns whether the url passed is a cross domain request or not.
6793
+ *
6794
+ * @param {string} url
6795
+ * The url to check.
6796
+ *
6797
+ * @return {boolean}
6798
+ * Whether it is a cross domain request or not.
6799
+ */
6800
+ var isCrossOrigin = function isCrossOrigin(url) {
6801
+ var winLoc = window_1.location;
6802
+ var urlInfo = parseUrl(url);
6803
+
6804
+ // IE8 protocol relative urls will return ':' for protocol
6805
+ var srcProtocol = urlInfo.protocol === ':' ? winLoc.protocol : urlInfo.protocol;
6806
+
6807
+ // Check if url is for another domain/origin
6808
+ // IE8 doesn't know location.origin, so we won't rely on it here
6809
+ var crossOrigin = srcProtocol + urlInfo.host !== winLoc.protocol + winLoc.host;
6810
+
6811
+ return crossOrigin;
6812
+ };
6813
+
6814
+ var Url = /*#__PURE__*/Object.freeze({
6815
+ parseUrl: parseUrl,
6816
+ getAbsoluteURL: getAbsoluteURL,
6817
+ getFileExtension: getFileExtension,
6818
+ isCrossOrigin: isCrossOrigin
6819
+ });
6820
+
6821
+ var isFunction_1 = isFunction;
6822
+
6823
+ var toString$1 = Object.prototype.toString;
6824
+
6825
+ function isFunction(fn) {
6826
+ var string = toString$1.call(fn);
6827
+ return string === '[object Function]' || typeof fn === 'function' && string !== '[object RegExp]' || typeof window !== 'undefined' && (
6828
+ // IE8 and below
6829
+ fn === window.setTimeout || fn === window.alert || fn === window.confirm || fn === window.prompt);
6830
+ }
6831
+
6832
+ var isFunction$1 = /*#__PURE__*/Object.freeze({
6833
+ default: isFunction_1,
6834
+ __moduleExports: isFunction_1
6835
+ });
6836
+
6837
+ var trim_1 = createCommonjsModule(function (module, exports) {
6838
+ exports = module.exports = trim;
6839
+
6840
+ function trim(str) {
6841
+ return str.replace(/^\s*|\s*$/g, '');
6842
+ }
6843
+
6844
+ exports.left = function (str) {
6845
+ return str.replace(/^\s*/, '');
6846
+ };
6847
+
6848
+ exports.right = function (str) {
6849
+ return str.replace(/\s*$/, '');
6850
+ };
6851
+ });
6852
+ var trim_2 = trim_1.left;
6853
+ var trim_3 = trim_1.right;
6854
+
6855
+ var trim = /*#__PURE__*/Object.freeze({
6856
+ default: trim_1,
6857
+ __moduleExports: trim_1,
6858
+ left: trim_2,
6859
+ right: trim_3
6860
+ });
6861
+
6862
+ var isFunction$2 = ( isFunction$1 && isFunction_1 ) || isFunction$1;
6863
+
6864
+ var forEach_1 = forEach;
6865
+
6866
+ var toString$2 = Object.prototype.toString;
6867
+ var hasOwnProperty = Object.prototype.hasOwnProperty;
6868
+
6869
+ function forEach(list, iterator, context) {
6870
+ if (!isFunction$2(iterator)) {
6871
+ throw new TypeError('iterator must be a function');
6872
+ }
6873
+
6874
+ if (arguments.length < 3) {
6875
+ context = this;
6876
+ }
6877
+
6878
+ if (toString$2.call(list) === '[object Array]') forEachArray(list, iterator, context);else if (typeof list === 'string') forEachString(list, iterator, context);else forEachObject(list, iterator, context);
6879
+ }
6880
+
6881
+ function forEachArray(array, iterator, context) {
6882
+ for (var i = 0, len = array.length; i < len; i++) {
6883
+ if (hasOwnProperty.call(array, i)) {
6884
+ iterator.call(context, array[i], i, array);
6885
+ }
6886
+ }
6887
+ }
6888
+
6889
+ function forEachString(string, iterator, context) {
6890
+ for (var i = 0, len = string.length; i < len; i++) {
6891
+ // no such thing as a sparse string.
6892
+ iterator.call(context, string.charAt(i), i, string);
6893
+ }
6894
+ }
6895
+
6896
+ function forEachObject(object, iterator, context) {
6897
+ for (var k in object) {
6898
+ if (hasOwnProperty.call(object, k)) {
6899
+ iterator.call(context, object[k], k, object);
6900
+ }
6901
+ }
6902
+ }
6903
+
6904
+ var forEach$1 = /*#__PURE__*/Object.freeze({
6905
+ default: forEach_1,
6906
+ __moduleExports: forEach_1
6907
+ });
6908
+
6909
+ var trim$1 = ( trim && trim_1 ) || trim;
6910
+
6911
+ var forEach$2 = ( forEach$1 && forEach_1 ) || forEach$1;
6912
+
6913
+ var isArray = function isArray(arg) {
6914
+ return Object.prototype.toString.call(arg) === '[object Array]';
6915
+ };
6916
+
6917
+ var parseHeaders = function parseHeaders(headers) {
6918
+ if (!headers) return {};
6919
+
6920
+ var result = {};
6921
+
6922
+ forEach$2(trim$1(headers).split('\n'), function (row) {
6923
+ var index = row.indexOf(':'),
6924
+ key = trim$1(row.slice(0, index)).toLowerCase(),
6925
+ value = trim$1(row.slice(index + 1));
6926
+
6927
+ if (typeof result[key] === 'undefined') {
6928
+ result[key] = value;
6929
+ } else if (isArray(result[key])) {
6930
+ result[key].push(value);
6931
+ } else {
6932
+ result[key] = [result[key], value];
6933
+ }
6934
+ });
6935
+
6936
+ return result;
6937
+ };
6938
+
6939
+ var parseHeaders$1 = /*#__PURE__*/Object.freeze({
6940
+ default: parseHeaders,
6941
+ __moduleExports: parseHeaders
6942
+ });
6943
+
6944
+ var immutable = extend;
6945
+
6946
+ var hasOwnProperty$1 = Object.prototype.hasOwnProperty;
6947
+
6948
+ function extend() {
6949
+ var target = {};
6950
+
6951
+ for (var i = 0; i < arguments.length; i++) {
6952
+ var source = arguments[i];
6953
+
6954
+ for (var key in source) {
6955
+ if (hasOwnProperty$1.call(source, key)) {
6956
+ target[key] = source[key];
6957
+ }
6958
+ }
6959
+ }
6960
+
6961
+ return target;
6962
+ }
6963
+
6964
+ var immutable$1 = /*#__PURE__*/Object.freeze({
6965
+ default: immutable,
6966
+ __moduleExports: immutable
6967
+ });
6968
+
6969
+ var parseHeaders$2 = ( parseHeaders$1 && parseHeaders ) || parseHeaders$1;
6970
+
6971
+ var xtend = ( immutable$1 && immutable ) || immutable$1;
6972
+
6973
+ var xhr = createXHR;
6974
+ createXHR.XMLHttpRequest = window_1.XMLHttpRequest || noop;
6975
+ createXHR.XDomainRequest = "withCredentials" in new createXHR.XMLHttpRequest() ? createXHR.XMLHttpRequest : window_1.XDomainRequest;
6976
+
6977
+ forEachArray$1(["get", "put", "post", "patch", "head", "delete"], function (method) {
6978
+ createXHR[method === "delete" ? "del" : method] = function (uri, options, callback) {
6979
+ options = initParams(uri, options, callback);
6980
+ options.method = method.toUpperCase();
6981
+ return _createXHR(options);
6982
+ };
6983
+ });
6984
+
6985
+ function forEachArray$1(array, iterator) {
6986
+ for (var i = 0; i < array.length; i++) {
6987
+ iterator(array[i]);
6988
+ }
6989
+ }
6990
+
6991
+ function isEmpty(obj) {
6992
+ for (var i in obj) {
6993
+ if (obj.hasOwnProperty(i)) return false;
6994
+ }
6995
+ return true;
6996
+ }
6997
+
6998
+ function initParams(uri, options, callback) {
6999
+ var params = uri;
7000
+
7001
+ if (isFunction$2(options)) {
7002
+ callback = options;
7003
+ if (typeof uri === "string") {
7004
+ params = { uri: uri };
7005
+ }
7006
+ } else {
7007
+ params = xtend(options, { uri: uri });
7008
+ }
7009
+
7010
+ params.callback = callback;
7011
+ return params;
7012
+ }
7013
+
7014
+ function createXHR(uri, options, callback) {
7015
+ options = initParams(uri, options, callback);
7016
+ return _createXHR(options);
7017
+ }
7018
+
7019
+ function _createXHR(options) {
7020
+ if (typeof options.callback === "undefined") {
7021
+ throw new Error("callback argument missing");
7022
+ }
7023
+
7024
+ var called = false;
7025
+ var callback = function cbOnce(err, response, body) {
7026
+ if (!called) {
7027
+ called = true;
7028
+ options.callback(err, response, body);
7029
+ }
7030
+ };
7031
+
7032
+ function readystatechange() {
7033
+ if (xhr.readyState === 4) {
7034
+ setTimeout(loadFunc, 0);
7035
+ }
7036
+ }
7037
+
7038
+ function getBody() {
7039
+ // Chrome with requestType=blob throws errors arround when even testing access to responseText
7040
+ var body = undefined;
7041
+
7042
+ if (xhr.response) {
7043
+ body = xhr.response;
7044
+ } else {
7045
+ body = xhr.responseText || getXml(xhr);
7046
+ }
7047
+
7048
+ if (isJson) {
7049
+ try {
7050
+ body = JSON.parse(body);
7051
+ } catch (e) {}
7052
+ }
7053
+
7054
+ return body;
7055
+ }
7056
+
7057
+ function errorFunc(evt) {
7058
+ clearTimeout(timeoutTimer);
7059
+ if (!(evt instanceof Error)) {
7060
+ evt = new Error("" + (evt || "Unknown XMLHttpRequest Error"));
7061
+ }
7062
+ evt.statusCode = 0;
7063
+ return callback(evt, failureResponse);
7064
+ }
7065
+
7066
+ // will load the data & process the response in a special response object
7067
+ function loadFunc() {
7068
+ if (aborted) return;
7069
+ var status;
7070
+ clearTimeout(timeoutTimer);
7071
+ if (options.useXDR && xhr.status === undefined) {
7072
+ //IE8 CORS GET successful response doesn't have a status field, but body is fine
7073
+ status = 200;
7074
+ } else {
7075
+ status = xhr.status === 1223 ? 204 : xhr.status;
7076
+ }
7077
+ var response = failureResponse;
7078
+ var err = null;
7079
+
7080
+ if (status !== 0) {
7081
+ response = {
7082
+ body: getBody(),
7083
+ statusCode: status,
7084
+ method: method,
7085
+ headers: {},
7086
+ url: uri,
7087
+ rawRequest: xhr
7088
+ };
7089
+ if (xhr.getAllResponseHeaders) {
7090
+ //remember xhr can in fact be XDR for CORS in IE
7091
+ response.headers = parseHeaders$2(xhr.getAllResponseHeaders());
7092
+ }
7093
+ } else {
7094
+ err = new Error("Internal XMLHttpRequest Error");
7095
+ }
7096
+ return callback(err, response, response.body);
7097
+ }
7098
+
7099
+ var xhr = options.xhr || null;
7100
+
7101
+ if (!xhr) {
7102
+ if (options.cors || options.useXDR) {
7103
+ xhr = new createXHR.XDomainRequest();
7104
+ } else {
7105
+ xhr = new createXHR.XMLHttpRequest();
7106
+ }
7107
+ }
7108
+
7109
+ var key;
7110
+ var aborted;
7111
+ var uri = xhr.url = options.uri || options.url;
7112
+ var method = xhr.method = options.method || "GET";
7113
+ var body = options.body || options.data;
7114
+ var headers = xhr.headers = options.headers || {};
7115
+ var sync = !!options.sync;
7116
+ var isJson = false;
7117
+ var timeoutTimer;
7118
+ var failureResponse = {
7119
+ body: undefined,
7120
+ headers: {},
7121
+ statusCode: 0,
7122
+ method: method,
7123
+ url: uri,
7124
+ rawRequest: xhr
7125
+ };
7126
+
7127
+ if ("json" in options && options.json !== false) {
7128
+ isJson = true;
7129
+ headers["accept"] || headers["Accept"] || (headers["Accept"] = "application/json"); //Don't override existing accept header declared by user
7130
+ if (method !== "GET" && method !== "HEAD") {
7131
+ headers["content-type"] || headers["Content-Type"] || (headers["Content-Type"] = "application/json"); //Don't override existing accept header declared by user
7132
+ body = JSON.stringify(options.json === true ? body : options.json);
7133
+ }
7134
+ }
7135
+
7136
+ xhr.onreadystatechange = readystatechange;
7137
+ xhr.onload = loadFunc;
7138
+ xhr.onerror = errorFunc;
7139
+ // IE9 must have onprogress be set to a unique function.
7140
+ xhr.onprogress = function () {
7141
+ // IE must die
7142
+ };
7143
+ xhr.onabort = function () {
7144
+ aborted = true;
7145
+ };
7146
+ xhr.ontimeout = errorFunc;
7147
+ xhr.open(method, uri, !sync, options.username, options.password);
7148
+ //has to be after open
7149
+ if (!sync) {
7150
+ xhr.withCredentials = !!options.withCredentials;
7151
+ }
7152
+ // Cannot set timeout with sync request
7153
+ // not setting timeout on the xhr object, because of old webkits etc. not handling that correctly
7154
+ // both npm's request and jquery 1.x use this kind of timeout, so this is being consistent
7155
+ if (!sync && options.timeout > 0) {
7156
+ timeoutTimer = setTimeout(function () {
7157
+ if (aborted) return;
7158
+ aborted = true; //IE9 may still call readystatechange
7159
+ xhr.abort("timeout");
7160
+ var e = new Error("XMLHttpRequest timeout");
7161
+ e.code = "ETIMEDOUT";
7162
+ errorFunc(e);
7163
+ }, options.timeout);
7164
+ }
7165
+
7166
+ if (xhr.setRequestHeader) {
7167
+ for (key in headers) {
7168
+ if (headers.hasOwnProperty(key)) {
7169
+ xhr.setRequestHeader(key, headers[key]);
7170
+ }
7171
+ }
7172
+ } else if (options.headers && !isEmpty(options.headers)) {
7173
+ throw new Error("Headers cannot be set on an XDomainRequest object");
7174
+ }
7175
+
7176
+ if ("responseType" in options) {
7177
+ xhr.responseType = options.responseType;
7178
+ }
7179
+
7180
+ if ("beforeSend" in options && typeof options.beforeSend === "function") {
7181
+ options.beforeSend(xhr);
7182
+ }
7183
+
7184
+ // Microsoft Edge browser sends "undefined" when send is called with undefined value.
7185
+ // XMLHttpRequest spec says to pass null as body to indicate no body
7186
+ // See https://github.com/naugtur/xhr/issues/100.
7187
+ xhr.send(body || null);
7188
+
7189
+ return xhr;
7190
+ }
7191
+
7192
+ function getXml(xhr) {
7193
+ if (xhr.responseType === "document") {
7194
+ return xhr.responseXML;
7195
+ }
7196
+ var firefoxBugTakenEffect = xhr.responseXML && xhr.responseXML.documentElement.nodeName === "parsererror";
7197
+ if (xhr.responseType === "" && !firefoxBugTakenEffect) {
7198
+ return xhr.responseXML;
7199
+ }
7200
+
7201
+ return null;
7202
+ }
7203
+
7204
+ function noop() {}
7205
+
7206
+ /**
7207
+ * @file text-track.js
7208
+ */
7209
+
7210
+ /**
7211
+ * Takes a webvtt file contents and parses it into cues
7212
+ *
7213
+ * @param {string} srcContent
7214
+ * webVTT file contents
7215
+ *
7216
+ * @param {TextTrack} track
7217
+ * TextTrack to add cues to. Cues come from the srcContent.
7218
+ *
7219
+ * @private
7220
+ */
7221
+ var parseCues = function parseCues(srcContent, track) {
7222
+ var parser = new window_1.WebVTT.Parser(window_1, window_1.vttjs, window_1.WebVTT.StringDecoder());
7223
+ var errors = [];
7224
+
7225
+ parser.oncue = function (cue) {
7226
+ track.addCue(cue);
7227
+ };
7228
+
7229
+ parser.onparsingerror = function (error) {
7230
+ errors.push(error);
7231
+ };
7232
+
7233
+ parser.onflush = function () {
7234
+ track.trigger({
7235
+ type: 'loadeddata',
7236
+ target: track
7237
+ });
7238
+ };
7239
+
7240
+ parser.parse(srcContent);
7241
+ if (errors.length > 0) {
7242
+ if (window_1.console && window_1.console.groupCollapsed) {
7243
+ window_1.console.groupCollapsed('Text Track parsing errors for ' + track.src);
7244
+ }
7245
+ errors.forEach(function (error) {
7246
+ return log$1.error(error);
7247
+ });
7248
+ if (window_1.console && window_1.console.groupEnd) {
7249
+ window_1.console.groupEnd();
7250
+ }
7251
+ }
7252
+
7253
+ parser.flush();
7254
+ };
7255
+
7256
+ /**
7257
+ * Load a `TextTrack` from a specified url.
7258
+ *
7259
+ * @param {string} src
7260
+ * Url to load track from.
7261
+ *
7262
+ * @param {TextTrack} track
7263
+ * Track to add cues to. Comes from the content at the end of `url`.
7264
+ *
7265
+ * @private
7266
+ */
7267
+ var loadTrack = function loadTrack(src, track) {
7268
+ var opts = {
7269
+ uri: src
7270
+ };
7271
+ var crossOrigin = isCrossOrigin(src);
7272
+
7273
+ if (crossOrigin) {
7274
+ opts.cors = crossOrigin;
7275
+ }
7276
+
7277
+ xhr(opts, bind(this, function (err, response, responseBody) {
7278
+ if (err) {
7279
+ return log$1.error(err, response);
7280
+ }
7281
+
7282
+ track.loaded_ = true;
7283
+
7284
+ // Make sure that vttjs has loaded, otherwise, wait till it finished loading
7285
+ // NOTE: this is only used for the alt/video.novtt.js build
7286
+ if (typeof window_1.WebVTT !== 'function') {
7287
+ if (track.tech_) {
7288
+ var loadHandler = function loadHandler() {
7289
+ return parseCues(responseBody, track);
7290
+ };
7291
+
7292
+ track.tech_.on('vttjsloaded', loadHandler);
7293
+ track.tech_.on('vttjserror', function () {
7294
+ log$1.error('vttjs failed to load, stopping trying to process ' + track.src);
7295
+ track.tech_.off('vttjsloaded', loadHandler);
7296
+ });
7297
+ }
7298
+ } else {
7299
+ parseCues(responseBody, track);
7300
+ }
7301
+ }));
7302
+ };
7303
+
7304
+ /**
7305
+ * A representation of a single `TextTrack`.
7306
+ *
7307
+ * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#texttrack}
7308
+ * @extends Track
7309
+ */
7310
+
7311
+ var TextTrack = function (_Track) {
7312
+ inherits(TextTrack, _Track);
7313
+
7314
+ /**
7315
+ * Create an instance of this class.
7316
+ *
7317
+ * @param {Object} options={}
7318
+ * Object of option names and values
7319
+ *
7320
+ * @param {Tech} options.tech
7321
+ * A reference to the tech that owns this TextTrack.
7322
+ *
7323
+ * @param {TextTrack~Kind} [options.kind='subtitles']
7324
+ * A valid text track kind.
7325
+ *
7326
+ * @param {TextTrack~Mode} [options.mode='disabled']
7327
+ * A valid text track mode.
7328
+ *
7329
+ * @param {string} [options.id='vjs_track_' + Guid.newGUID()]
7330
+ * A unique id for this TextTrack.
7331
+ *
7332
+ * @param {string} [options.label='']
7333
+ * The menu label for this track.
7334
+ *
7335
+ * @param {string} [options.language='']
7336
+ * A valid two character language code.
7337
+ *
7338
+ * @param {string} [options.srclang='']
7339
+ * A valid two character language code. An alternative, but deprioritized
7340
+ * version of `options.language`
7341
+ *
7342
+ * @param {string} [options.src]
7343
+ * A url to TextTrack cues.
7344
+ *
7345
+ * @param {boolean} [options.default]
7346
+ * If this track should default to on or off.
7347
+ */
7348
+ function TextTrack() {
7349
+ var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
7350
+ classCallCheck(this, TextTrack);
7351
+
7352
+ if (!options.tech) {
7353
+ throw new Error('A tech was not provided.');
7354
+ }
7355
+
7356
+ var settings = mergeOptions(options, {
7357
+ kind: TextTrackKind[options.kind] || 'subtitles',
7358
+ language: options.language || options.srclang || ''
7359
+ });
7360
+ var mode = TextTrackMode[settings.mode] || 'disabled';
7361
+ var default_ = settings.default;
7362
+
7363
+ if (settings.kind === 'metadata' || settings.kind === 'chapters') {
7364
+ mode = 'hidden';
7365
+ }
7366
+
7367
+ var _this = possibleConstructorReturn(this, _Track.call(this, settings));
7368
+
7369
+ _this.tech_ = settings.tech;
7370
+
7371
+ _this.cues_ = [];
7372
+ _this.activeCues_ = [];
7373
+
7374
+ var cues = new TextTrackCueList(_this.cues_);
7375
+ var activeCues = new TextTrackCueList(_this.activeCues_);
7376
+ var changed = false;
7377
+ var timeupdateHandler = bind(_this, function () {
7378
+
7379
+ // Accessing this.activeCues for the side-effects of updating itself
7380
+ // due to it's nature as a getter function. Do not remove or cues will
7381
+ // stop updating!
7382
+ /* eslint-disable no-unused-expressions */
7383
+ this.activeCues;
7384
+ /* eslint-enable no-unused-expressions */
7385
+ if (changed) {
7386
+ this.trigger('cuechange');
7387
+ changed = false;
7388
+ }
7389
+ });
7390
+
7391
+ if (mode !== 'disabled') {
7392
+ _this.tech_.ready(function () {
7393
+ _this.tech_.on('timeupdate', timeupdateHandler);
7394
+ }, true);
7395
+ }
7396
+
7397
+ Object.defineProperties(_this, {
7398
+ /**
7399
+ * @memberof TextTrack
7400
+ * @member {boolean} default
7401
+ * If this track was set to be on or off by default. Cannot be changed after
7402
+ * creation.
7403
+ * @instance
7404
+ *
7405
+ * @readonly
7406
+ */
7407
+ default: {
7408
+ get: function get$$1() {
7409
+ return default_;
7410
+ },
7411
+ set: function set$$1() {}
7412
+ },
7413
+
7414
+ /**
7415
+ * @memberof TextTrack
7416
+ * @member {string} mode
7417
+ * Set the mode of this TextTrack to a valid {@link TextTrack~Mode}. Will
7418
+ * not be set if setting to an invalid mode.
7419
+ * @instance
7420
+ *
7421
+ * @fires TextTrack#modechange
7422
+ */
7423
+ mode: {
7424
+ get: function get$$1() {
7425
+ return mode;
7426
+ },
7427
+ set: function set$$1(newMode) {
7428
+ var _this2 = this;
7429
+
7430
+ if (!TextTrackMode[newMode]) {
7431
+ return;
7432
+ }
7433
+ mode = newMode;
7434
+ if (mode === 'showing') {
7435
+
7436
+ this.tech_.ready(function () {
7437
+ _this2.tech_.on('timeupdate', timeupdateHandler);
7438
+ }, true);
7439
+ }
7440
+ /**
7441
+ * An event that fires when mode changes on this track. This allows
7442
+ * the TextTrackList that holds this track to act accordingly.
7443
+ *
7444
+ * > Note: This is not part of the spec!
7445
+ *
7446
+ * @event TextTrack#modechange
7447
+ * @type {EventTarget~Event}
7448
+ */
7449
+ this.trigger('modechange');
7450
+ }
7451
+ },
7452
+
7453
+ /**
7454
+ * @memberof TextTrack
7455
+ * @member {TextTrackCueList} cues
7456
+ * The text track cue list for this TextTrack.
7457
+ * @instance
7458
+ */
7459
+ cues: {
7460
+ get: function get$$1() {
7461
+ if (!this.loaded_) {
7462
+ return null;
7463
+ }
7464
+
7465
+ return cues;
7466
+ },
7467
+ set: function set$$1() {}
7468
+ },
7469
+
7470
+ /**
7471
+ * @memberof TextTrack
7472
+ * @member {TextTrackCueList} activeCues
7473
+ * The list text track cues that are currently active for this TextTrack.
7474
+ * @instance
7475
+ */
7476
+ activeCues: {
7477
+ get: function get$$1() {
7478
+ if (!this.loaded_) {
7479
+ return null;
7480
+ }
7481
+
7482
+ // nothing to do
7483
+ if (this.cues.length === 0) {
7484
+ return activeCues;
7485
+ }
7486
+
7487
+ var ct = this.tech_.currentTime();
7488
+ var active = [];
7489
+
7490
+ for (var i = 0, l = this.cues.length; i < l; i++) {
7491
+ var cue = this.cues[i];
7492
+
7493
+ if (cue.startTime <= ct && cue.endTime >= ct) {
7494
+ active.push(cue);
7495
+ } else if (cue.startTime === cue.endTime && cue.startTime <= ct && cue.startTime + 0.5 >= ct) {
7496
+ active.push(cue);
7497
+ }
7498
+ }
7499
+
7500
+ changed = false;
7501
+
7502
+ if (active.length !== this.activeCues_.length) {
7503
+ changed = true;
7504
+ } else {
7505
+ for (var _i = 0; _i < active.length; _i++) {
7506
+ if (this.activeCues_.indexOf(active[_i]) === -1) {
7507
+ changed = true;
7508
+ }
7509
+ }
7510
+ }
7511
+
7512
+ this.activeCues_ = active;
7513
+ activeCues.setCues_(this.activeCues_);
7514
+
7515
+ return activeCues;
7516
+ },
7517
+ set: function set$$1() {}
7518
+ }
7519
+ });
7520
+
7521
+ if (settings.src) {
7522
+ _this.src = settings.src;
7523
+ loadTrack(settings.src, _this);
7524
+ } else {
7525
+ _this.loaded_ = true;
7526
+ }
7527
+ return _this;
7528
+ }
7529
+
7530
+ /**
7531
+ * Add a cue to the internal list of cues.
7532
+ *
7533
+ * @param {TextTrack~Cue} cue
7534
+ * The cue to add to our internal list
7535
+ */
7536
+
7537
+
7538
+ TextTrack.prototype.addCue = function addCue(originalCue) {
7539
+ var cue = originalCue;
7540
+
7541
+ if (window_1.vttjs && !(originalCue instanceof window_1.vttjs.VTTCue)) {
7542
+ cue = new window_1.vttjs.VTTCue(originalCue.startTime, originalCue.endTime, originalCue.text);
7543
+
7544
+ for (var prop in originalCue) {
7545
+ if (!(prop in cue)) {
7546
+ cue[prop] = originalCue[prop];
7547
+ }
7548
+ }
7549
+
7550
+ // make sure that `id` is copied over
7551
+ cue.id = originalCue.id;
7552
+ cue.originalCue_ = originalCue;
7553
+ }
7554
+
7555
+ var tracks = this.tech_.textTracks();
7556
+
7557
+ for (var i = 0; i < tracks.length; i++) {
7558
+ if (tracks[i] !== this) {
7559
+ tracks[i].removeCue(cue);
7560
+ }
7561
+ }
7562
+
7563
+ this.cues_.push(cue);
7564
+ this.cues.setCues_(this.cues_);
7565
+ };
7566
+
7567
+ /**
7568
+ * Remove a cue from our internal list
7569
+ *
7570
+ * @param {TextTrack~Cue} removeCue
7571
+ * The cue to remove from our internal list
7572
+ */
7573
+
7574
+
7575
+ TextTrack.prototype.removeCue = function removeCue(_removeCue) {
7576
+ var i = this.cues_.length;
7577
+
7578
+ while (i--) {
7579
+ var cue = this.cues_[i];
7580
+
7581
+ if (cue === _removeCue || cue.originalCue_ && cue.originalCue_ === _removeCue) {
7582
+ this.cues_.splice(i, 1);
7583
+ this.cues.setCues_(this.cues_);
7584
+ break;
7585
+ }
7586
+ }
7587
+ };
7588
+
7589
+ return TextTrack;
7590
+ }(Track);
7591
+
7592
+ /**
7593
+ * cuechange - One or more cues in the track have become active or stopped being active.
7594
+ */
7595
+
7596
+
7597
+ TextTrack.prototype.allowedEvents_ = {
7598
+ cuechange: 'cuechange'
7599
+ };
7600
+
7601
+ /**
7602
+ * A representation of a single `AudioTrack`. If it is part of an {@link AudioTrackList}
7603
+ * only one `AudioTrack` in the list will be enabled at a time.
7604
+ *
7605
+ * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#audiotrack}
7606
+ * @extends Track
7607
+ */
7608
+
7609
+ var AudioTrack = function (_Track) {
7610
+ inherits(AudioTrack, _Track);
7611
+
7612
+ /**
7613
+ * Create an instance of this class.
7614
+ *
7615
+ * @param {Object} [options={}]
7616
+ * Object of option names and values
7617
+ *
7618
+ * @param {AudioTrack~Kind} [options.kind='']
7619
+ * A valid audio track kind
7620
+ *
7621
+ * @param {string} [options.id='vjs_track_' + Guid.newGUID()]
7622
+ * A unique id for this AudioTrack.
7623
+ *
7624
+ * @param {string} [options.label='']
7625
+ * The menu label for this track.
7626
+ *
7627
+ * @param {string} [options.language='']
7628
+ * A valid two character language code.
7629
+ *
7630
+ * @param {boolean} [options.enabled]
7631
+ * If this track is the one that is currently playing. If this track is part of
7632
+ * an {@link AudioTrackList}, only one {@link AudioTrack} will be enabled.
7633
+ */
7634
+ function AudioTrack() {
7635
+ var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
7636
+ classCallCheck(this, AudioTrack);
7637
+
7638
+ var settings = mergeOptions(options, {
7639
+ kind: AudioTrackKind[options.kind] || ''
7640
+ });
7641
+
7642
+ var _this = possibleConstructorReturn(this, _Track.call(this, settings));
7643
+
7644
+ var enabled = false;
7645
+
7646
+ /**
7647
+ * @memberof AudioTrack
7648
+ * @member {boolean} enabled
7649
+ * If this `AudioTrack` is enabled or not. When setting this will
7650
+ * fire {@link AudioTrack#enabledchange} if the state of enabled is changed.
7651
+ * @instance
7652
+ *
7653
+ * @fires VideoTrack#selectedchange
7654
+ */
7655
+ Object.defineProperty(_this, 'enabled', {
7656
+ get: function get$$1() {
7657
+ return enabled;
7658
+ },
7659
+ set: function set$$1(newEnabled) {
7660
+ // an invalid or unchanged value
7661
+ if (typeof newEnabled !== 'boolean' || newEnabled === enabled) {
7662
+ return;
7663
+ }
7664
+ enabled = newEnabled;
7665
+
7666
+ /**
7667
+ * An event that fires when enabled changes on this track. This allows
7668
+ * the AudioTrackList that holds this track to act accordingly.
7669
+ *
7670
+ * > Note: This is not part of the spec! Native tracks will do
7671
+ * this internally without an event.
7672
+ *
7673
+ * @event AudioTrack#enabledchange
7674
+ * @type {EventTarget~Event}
7675
+ */
7676
+ this.trigger('enabledchange');
7677
+ }
7678
+ });
7679
+
7680
+ // if the user sets this track to selected then
7681
+ // set selected to that true value otherwise
7682
+ // we keep it false
7683
+ if (settings.enabled) {
7684
+ _this.enabled = settings.enabled;
7685
+ }
7686
+ _this.loaded_ = true;
7687
+ return _this;
7688
+ }
7689
+
7690
+ return AudioTrack;
7691
+ }(Track);
7692
+
7693
+ /**
7694
+ * A representation of a single `VideoTrack`.
7695
+ *
7696
+ * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#videotrack}
7697
+ * @extends Track
7698
+ */
7699
+
7700
+ var VideoTrack = function (_Track) {
7701
+ inherits(VideoTrack, _Track);
7702
+
7703
+ /**
7704
+ * Create an instance of this class.
7705
+ *
7706
+ * @param {Object} [options={}]
7707
+ * Object of option names and values
7708
+ *
7709
+ * @param {string} [options.kind='']
7710
+ * A valid {@link VideoTrack~Kind}
7711
+ *
7712
+ * @param {string} [options.id='vjs_track_' + Guid.newGUID()]
7713
+ * A unique id for this AudioTrack.
7714
+ *
7715
+ * @param {string} [options.label='']
7716
+ * The menu label for this track.
7717
+ *
7718
+ * @param {string} [options.language='']
7719
+ * A valid two character language code.
7720
+ *
7721
+ * @param {boolean} [options.selected]
7722
+ * If this track is the one that is currently playing.
7723
+ */
7724
+ function VideoTrack() {
7725
+ var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
7726
+ classCallCheck(this, VideoTrack);
7727
+
7728
+ var settings = mergeOptions(options, {
7729
+ kind: VideoTrackKind[options.kind] || ''
7730
+ });
7731
+
7732
+ var _this = possibleConstructorReturn(this, _Track.call(this, settings));
7733
+
7734
+ var selected = false;
7735
+
7736
+ /**
7737
+ * @memberof VideoTrack
7738
+ * @member {boolean} selected
7739
+ * If this `VideoTrack` is selected or not. When setting this will
7740
+ * fire {@link VideoTrack#selectedchange} if the state of selected changed.
7741
+ * @instance
7742
+ *
7743
+ * @fires VideoTrack#selectedchange
7744
+ */
7745
+ Object.defineProperty(_this, 'selected', {
7746
+ get: function get$$1() {
7747
+ return selected;
7748
+ },
7749
+ set: function set$$1(newSelected) {
7750
+ // an invalid or unchanged value
7751
+ if (typeof newSelected !== 'boolean' || newSelected === selected) {
7752
+ return;
7753
+ }
7754
+ selected = newSelected;
7755
+
7756
+ /**
7757
+ * An event that fires when selected changes on this track. This allows
7758
+ * the VideoTrackList that holds this track to act accordingly.
7759
+ *
7760
+ * > Note: This is not part of the spec! Native tracks will do
7761
+ * this internally without an event.
7762
+ *
7763
+ * @event VideoTrack#selectedchange
7764
+ * @type {EventTarget~Event}
7765
+ */
7766
+ this.trigger('selectedchange');
7767
+ }
7768
+ });
7769
+
7770
+ // if the user sets this track to selected then
7771
+ // set selected to that true value otherwise
7772
+ // we keep it false
7773
+ if (settings.selected) {
7774
+ _this.selected = settings.selected;
7775
+ }
7776
+ return _this;
7777
+ }
7778
+
7779
+ return VideoTrack;
7780
+ }(Track);
7781
+
7782
+ /**
7783
+ * @file html-track-element.js
7784
+ */
7785
+
7786
+ /**
7787
+ * @memberof HTMLTrackElement
7788
+ * @typedef {HTMLTrackElement~ReadyState}
7789
+ * @enum {number}
7790
+ */
7791
+ var NONE = 0;
7792
+ var LOADING = 1;
7793
+ var LOADED = 2;
7794
+ var ERROR = 3;
7795
+
7796
+ /**
7797
+ * A single track represented in the DOM.
7798
+ *
7799
+ * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#htmltrackelement}
7800
+ * @extends EventTarget
7801
+ */
7802
+
7803
+ var HTMLTrackElement = function (_EventTarget) {
7804
+ inherits(HTMLTrackElement, _EventTarget);
7805
+
7806
+ /**
7807
+ * Create an instance of this class.
7808
+ *
7809
+ * @param {Object} options={}
7810
+ * Object of option names and values
7811
+ *
7812
+ * @param {Tech} options.tech
7813
+ * A reference to the tech that owns this HTMLTrackElement.
7814
+ *
7815
+ * @param {TextTrack~Kind} [options.kind='subtitles']
7816
+ * A valid text track kind.
7817
+ *
7818
+ * @param {TextTrack~Mode} [options.mode='disabled']
7819
+ * A valid text track mode.
7820
+ *
7821
+ * @param {string} [options.id='vjs_track_' + Guid.newGUID()]
7822
+ * A unique id for this TextTrack.
7823
+ *
7824
+ * @param {string} [options.label='']
7825
+ * The menu label for this track.
7826
+ *
7827
+ * @param {string} [options.language='']
7828
+ * A valid two character language code.
7829
+ *
7830
+ * @param {string} [options.srclang='']
7831
+ * A valid two character language code. An alternative, but deprioritized
7832
+ * vesion of `options.language`
7833
+ *
7834
+ * @param {string} [options.src]
7835
+ * A url to TextTrack cues.
7836
+ *
7837
+ * @param {boolean} [options.default]
7838
+ * If this track should default to on or off.
7839
+ */
7840
+ function HTMLTrackElement() {
7841
+ var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
7842
+ classCallCheck(this, HTMLTrackElement);
7843
+
7844
+ var _this = possibleConstructorReturn(this, _EventTarget.call(this));
7845
+
7846
+ var readyState = void 0;
7847
+
7848
+ var track = new TextTrack(options);
7849
+
7850
+ _this.kind = track.kind;
7851
+ _this.src = track.src;
7852
+ _this.srclang = track.language;
7853
+ _this.label = track.label;
7854
+ _this.default = track.default;
7855
+
7856
+ Object.defineProperties(_this, {
7857
+
7858
+ /**
7859
+ * @memberof HTMLTrackElement
7860
+ * @member {HTMLTrackElement~ReadyState} readyState
7861
+ * The current ready state of the track element.
7862
+ * @instance
7863
+ */
7864
+ readyState: {
7865
+ get: function get$$1() {
7866
+ return readyState;
7867
+ }
7868
+ },
7869
+
7870
+ /**
7871
+ * @memberof HTMLTrackElement
7872
+ * @member {TextTrack} track
7873
+ * The underlying TextTrack object.
7874
+ * @instance
7875
+ *
7876
+ */
7877
+ track: {
7878
+ get: function get$$1() {
7879
+ return track;
7880
+ }
7881
+ }
7882
+ });
7883
+
7884
+ readyState = NONE;
7885
+
7886
+ /**
7887
+ * @listens TextTrack#loadeddata
7888
+ * @fires HTMLTrackElement#load
7889
+ */
7890
+ track.addEventListener('loadeddata', function () {
7891
+ readyState = LOADED;
7892
+
7893
+ _this.trigger({
7894
+ type: 'load',
7895
+ target: _this
7896
+ });
7897
+ });
7898
+ return _this;
7899
+ }
7900
+
7901
+ return HTMLTrackElement;
7902
+ }(EventTarget);
7903
+
7904
+ HTMLTrackElement.prototype.allowedEvents_ = {
7905
+ load: 'load'
7906
+ };
7907
+
7908
+ HTMLTrackElement.NONE = NONE;
7909
+ HTMLTrackElement.LOADING = LOADING;
7910
+ HTMLTrackElement.LOADED = LOADED;
7911
+ HTMLTrackElement.ERROR = ERROR;
7912
+
7913
+ /*
7914
+ * This file contains all track properties that are used in
7915
+ * player.js, tech.js, html5.js and possibly other techs in the future.
7916
+ */
7917
+
7918
+ var NORMAL = {
7919
+ audio: {
7920
+ ListClass: AudioTrackList,
7921
+ TrackClass: AudioTrack,
7922
+ capitalName: 'Audio'
7923
+ },
7924
+ video: {
7925
+ ListClass: VideoTrackList,
7926
+ TrackClass: VideoTrack,
7927
+ capitalName: 'Video'
7928
+ },
7929
+ text: {
7930
+ ListClass: TextTrackList,
7931
+ TrackClass: TextTrack,
7932
+ capitalName: 'Text'
7933
+ }
7934
+ };
7935
+
7936
+ Object.keys(NORMAL).forEach(function (type) {
7937
+ NORMAL[type].getterName = type + 'Tracks';
7938
+ NORMAL[type].privateName = type + 'Tracks_';
7939
+ });
7940
+
7941
+ var REMOTE = {
7942
+ remoteText: {
7943
+ ListClass: TextTrackList,
7944
+ TrackClass: TextTrack,
7945
+ capitalName: 'RemoteText',
7946
+ getterName: 'remoteTextTracks',
7947
+ privateName: 'remoteTextTracks_'
7948
+ },
7949
+ remoteTextEl: {
7950
+ ListClass: HtmlTrackElementList,
7951
+ TrackClass: HTMLTrackElement,
7952
+ capitalName: 'RemoteTextTrackEls',
7953
+ getterName: 'remoteTextTrackEls',
7954
+ privateName: 'remoteTextTrackEls_'
7955
+ }
7956
+ };
7957
+
7958
+ var ALL = mergeOptions(NORMAL, REMOTE);
7959
+
7960
+ REMOTE.names = Object.keys(REMOTE);
7961
+ NORMAL.names = Object.keys(NORMAL);
7962
+ ALL.names = [].concat(REMOTE.names).concat(NORMAL.names);
7963
+
7964
+ /**
7965
+ * Copyright 2013 vtt.js Contributors
7966
+ *
7967
+ * Licensed under the Apache License, Version 2.0 (the "License");
7968
+ * you may not use this file except in compliance with the License.
7969
+ * You may obtain a copy of the License at
7970
+ *
7971
+ * http://www.apache.org/licenses/LICENSE-2.0
7972
+ *
7973
+ * Unless required by applicable law or agreed to in writing, software
7974
+ * distributed under the License is distributed on an "AS IS" BASIS,
7975
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
7976
+ * See the License for the specific language governing permissions and
7977
+ * limitations under the License.
7978
+ */
7979
+
7980
+ /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
7981
+ /* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
7982
+ var _objCreate = Object.create || function () {
7983
+ function F() {}
7984
+ return function (o) {
7985
+ if (arguments.length !== 1) {
7986
+ throw new Error('Object.create shim only accepts one parameter.');
7987
+ }
7988
+ F.prototype = o;
7989
+ return new F();
7990
+ };
7991
+ }();
7992
+
7993
+ // Creates a new ParserError object from an errorData object. The errorData
7994
+ // object should have default code and message properties. The default message
7995
+ // property can be overriden by passing in a message parameter.
7996
+ // See ParsingError.Errors below for acceptable errors.
7997
+ function ParsingError(errorData, message) {
7998
+ this.name = "ParsingError";
7999
+ this.code = errorData.code;
8000
+ this.message = message || errorData.message;
8001
+ }
8002
+ ParsingError.prototype = _objCreate(Error.prototype);
8003
+ ParsingError.prototype.constructor = ParsingError;
8004
+
8005
+ // ParsingError metadata for acceptable ParsingErrors.
8006
+ ParsingError.Errors = {
8007
+ BadSignature: {
8008
+ code: 0,
8009
+ message: "Malformed WebVTT signature."
8010
+ },
8011
+ BadTimeStamp: {
8012
+ code: 1,
8013
+ message: "Malformed time stamp."
8014
+ }
8015
+ };
8016
+
8017
+ // Try to parse input as a time stamp.
8018
+ function parseTimeStamp(input) {
8019
+
8020
+ function computeSeconds(h, m, s, f) {
8021
+ return (h | 0) * 3600 + (m | 0) * 60 + (s | 0) + (f | 0) / 1000;
8022
+ }
8023
+
8024
+ var m = input.match(/^(\d+):(\d{2})(:\d{2})?\.(\d{3})/);
8025
+ if (!m) {
8026
+ return null;
8027
+ }
8028
+
8029
+ if (m[3]) {
8030
+ // Timestamp takes the form of [hours]:[minutes]:[seconds].[milliseconds]
8031
+ return computeSeconds(m[1], m[2], m[3].replace(":", ""), m[4]);
8032
+ } else if (m[1] > 59) {
8033
+ // Timestamp takes the form of [hours]:[minutes].[milliseconds]
8034
+ // First position is hours as it's over 59.
8035
+ return computeSeconds(m[1], m[2], 0, m[4]);
8036
+ } else {
8037
+ // Timestamp takes the form of [minutes]:[seconds].[milliseconds]
8038
+ return computeSeconds(0, m[1], m[2], m[4]);
8039
+ }
8040
+ }
8041
+
8042
+ // A settings object holds key/value pairs and will ignore anything but the first
8043
+ // assignment to a specific key.
8044
+ function Settings() {
8045
+ this.values = _objCreate(null);
8046
+ }
8047
+
8048
+ Settings.prototype = {
8049
+ // Only accept the first assignment to any key.
8050
+ set: function set(k, v) {
8051
+ if (!this.get(k) && v !== "") {
8052
+ this.values[k] = v;
8053
+ }
8054
+ },
8055
+ // Return the value for a key, or a default value.
8056
+ // If 'defaultKey' is passed then 'dflt' is assumed to be an object with
8057
+ // a number of possible default values as properties where 'defaultKey' is
8058
+ // the key of the property that will be chosen; otherwise it's assumed to be
8059
+ // a single value.
8060
+ get: function get(k, dflt, defaultKey) {
8061
+ if (defaultKey) {
8062
+ return this.has(k) ? this.values[k] : dflt[defaultKey];
8063
+ }
8064
+ return this.has(k) ? this.values[k] : dflt;
8065
+ },
8066
+ // Check whether we have a value for a key.
8067
+ has: function has(k) {
8068
+ return k in this.values;
8069
+ },
8070
+ // Accept a setting if its one of the given alternatives.
8071
+ alt: function alt(k, v, a) {
8072
+ for (var n = 0; n < a.length; ++n) {
8073
+ if (v === a[n]) {
8074
+ this.set(k, v);
8075
+ break;
8076
+ }
8077
+ }
8078
+ },
8079
+ // Accept a setting if its a valid (signed) integer.
8080
+ integer: function integer(k, v) {
8081
+ if (/^-?\d+$/.test(v)) {
8082
+ // integer
8083
+ this.set(k, parseInt(v, 10));
8084
+ }
8085
+ },
8086
+ // Accept a setting if its a valid percentage.
8087
+ percent: function percent(k, v) {
8088
+ var m;
8089
+ if (m = v.match(/^([\d]{1,3})(\.[\d]*)?%$/)) {
8090
+ v = parseFloat(v);
8091
+ if (v >= 0 && v <= 100) {
8092
+ this.set(k, v);
8093
+ return true;
8094
+ }
8095
+ }
8096
+ return false;
8097
+ }
8098
+ };
8099
+
8100
+ // Helper function to parse input into groups separated by 'groupDelim', and
8101
+ // interprete each group as a key/value pair separated by 'keyValueDelim'.
8102
+ function parseOptions(input, callback, keyValueDelim, groupDelim) {
8103
+ var groups = groupDelim ? input.split(groupDelim) : [input];
8104
+ for (var i in groups) {
8105
+ if (typeof groups[i] !== "string") {
8106
+ continue;
8107
+ }
8108
+ var kv = groups[i].split(keyValueDelim);
8109
+ if (kv.length !== 2) {
8110
+ continue;
8111
+ }
8112
+ var k = kv[0];
8113
+ var v = kv[1];
8114
+ callback(k, v);
8115
+ }
8116
+ }
8117
+
8118
+ function parseCue(input, cue, regionList) {
8119
+ // Remember the original input if we need to throw an error.
8120
+ var oInput = input;
8121
+ // 4.1 WebVTT timestamp
8122
+ function consumeTimeStamp() {
8123
+ var ts = parseTimeStamp(input);
8124
+ if (ts === null) {
8125
+ throw new ParsingError(ParsingError.Errors.BadTimeStamp, "Malformed timestamp: " + oInput);
8126
+ }
8127
+ // Remove time stamp from input.
8128
+ input = input.replace(/^[^\sa-zA-Z-]+/, "");
8129
+ return ts;
8130
+ }
8131
+
8132
+ // 4.4.2 WebVTT cue settings
8133
+ function consumeCueSettings(input, cue) {
8134
+ var settings = new Settings();
8135
+
8136
+ parseOptions(input, function (k, v) {
8137
+ switch (k) {
8138
+ case "region":
8139
+ // Find the last region we parsed with the same region id.
8140
+ for (var i = regionList.length - 1; i >= 0; i--) {
8141
+ if (regionList[i].id === v) {
8142
+ settings.set(k, regionList[i].region);
8143
+ break;
8144
+ }
8145
+ }
8146
+ break;
8147
+ case "vertical":
8148
+ settings.alt(k, v, ["rl", "lr"]);
8149
+ break;
8150
+ case "line":
8151
+ var vals = v.split(","),
8152
+ vals0 = vals[0];
8153
+ settings.integer(k, vals0);
8154
+ settings.percent(k, vals0) ? settings.set("snapToLines", false) : null;
8155
+ settings.alt(k, vals0, ["auto"]);
8156
+ if (vals.length === 2) {
8157
+ settings.alt("lineAlign", vals[1], ["start", "middle", "end"]);
8158
+ }
8159
+ break;
8160
+ case "position":
8161
+ vals = v.split(",");
8162
+ settings.percent(k, vals[0]);
8163
+ if (vals.length === 2) {
8164
+ settings.alt("positionAlign", vals[1], ["start", "middle", "end"]);
8165
+ }
8166
+ break;
8167
+ case "size":
8168
+ settings.percent(k, v);
8169
+ break;
8170
+ case "align":
8171
+ settings.alt(k, v, ["start", "middle", "end", "left", "right"]);
8172
+ break;
8173
+ }
8174
+ }, /:/, /\s/);
8175
+
8176
+ // Apply default values for any missing fields.
8177
+ cue.region = settings.get("region", null);
8178
+ cue.vertical = settings.get("vertical", "");
8179
+ cue.line = settings.get("line", "auto");
8180
+ cue.lineAlign = settings.get("lineAlign", "start");
8181
+ cue.snapToLines = settings.get("snapToLines", true);
8182
+ cue.size = settings.get("size", 100);
8183
+ cue.align = settings.get("align", "middle");
8184
+ cue.position = settings.get("position", {
8185
+ start: 0,
8186
+ left: 0,
8187
+ middle: 50,
8188
+ end: 100,
8189
+ right: 100
8190
+ }, cue.align);
8191
+ cue.positionAlign = settings.get("positionAlign", {
8192
+ start: "start",
8193
+ left: "start",
8194
+ middle: "middle",
8195
+ end: "end",
8196
+ right: "end"
8197
+ }, cue.align);
8198
+ }
8199
+
8200
+ function skipWhitespace() {
8201
+ input = input.replace(/^\s+/, "");
8202
+ }
8203
+
8204
+ // 4.1 WebVTT cue timings.
8205
+ skipWhitespace();
8206
+ cue.startTime = consumeTimeStamp(); // (1) collect cue start time
8207
+ skipWhitespace();
8208
+ if (input.substr(0, 3) !== "-->") {
8209
+ // (3) next characters must match "-->"
8210
+ throw new ParsingError(ParsingError.Errors.BadTimeStamp, "Malformed time stamp (time stamps must be separated by '-->'): " + oInput);
8211
+ }
8212
+ input = input.substr(3);
8213
+ skipWhitespace();
8214
+ cue.endTime = consumeTimeStamp(); // (5) collect cue end time
8215
+
8216
+ // 4.1 WebVTT cue settings list.
8217
+ skipWhitespace();
8218
+ consumeCueSettings(input, cue);
8219
+ }
8220
+
8221
+ var ESCAPE = {
8222
+ "&amp;": "&",
8223
+ "&lt;": "<",
8224
+ "&gt;": ">",
8225
+ "&lrm;": "\u200E",
8226
+ "&rlm;": "\u200F",
8227
+ "&nbsp;": "\xA0"
8228
+ };
8229
+
8230
+ var TAG_NAME = {
8231
+ c: "span",
8232
+ i: "i",
8233
+ b: "b",
8234
+ u: "u",
8235
+ ruby: "ruby",
8236
+ rt: "rt",
8237
+ v: "span",
8238
+ lang: "span"
8239
+ };
8240
+
8241
+ var TAG_ANNOTATION = {
8242
+ v: "title",
8243
+ lang: "lang"
8244
+ };
8245
+
8246
+ var NEEDS_PARENT = {
8247
+ rt: "ruby"
8248
+ };
8249
+
8250
+ // Parse content into a document fragment.
8251
+ function parseContent(window, input) {
8252
+ function nextToken() {
8253
+ // Check for end-of-string.
8254
+ if (!input) {
8255
+ return null;
8256
+ }
8257
+
8258
+ // Consume 'n' characters from the input.
8259
+ function consume(result) {
8260
+ input = input.substr(result.length);
8261
+ return result;
8262
+ }
8263
+
8264
+ var m = input.match(/^([^<]*)(<[^>]*>?)?/);
8265
+ // If there is some text before the next tag, return it, otherwise return
8266
+ // the tag.
8267
+ return consume(m[1] ? m[1] : m[2]);
8268
+ }
8269
+
8270
+ // Unescape a string 's'.
8271
+ function unescape1(e) {
8272
+ return ESCAPE[e];
8273
+ }
8274
+ function unescape(s) {
8275
+ while (m = s.match(/&(amp|lt|gt|lrm|rlm|nbsp);/)) {
8276
+ s = s.replace(m[0], unescape1);
8277
+ }
8278
+ return s;
8279
+ }
8280
+
8281
+ function shouldAdd(current, element) {
8282
+ return !NEEDS_PARENT[element.localName] || NEEDS_PARENT[element.localName] === current.localName;
8283
+ }
8284
+
8285
+ // Create an element for this tag.
8286
+ function createElement(type, annotation) {
8287
+ var tagName = TAG_NAME[type];
8288
+ if (!tagName) {
8289
+ return null;
8290
+ }
8291
+ var element = window.document.createElement(tagName);
8292
+ element.localName = tagName;
8293
+ var name = TAG_ANNOTATION[type];
8294
+ if (name && annotation) {
8295
+ element[name] = annotation.trim();
8296
+ }
8297
+ return element;
8298
+ }
8299
+
8300
+ var rootDiv = window.document.createElement("div"),
8301
+ current = rootDiv,
8302
+ t,
8303
+ tagStack = [];
8304
+
8305
+ while ((t = nextToken()) !== null) {
8306
+ if (t[0] === '<') {
8307
+ if (t[1] === "/") {
8308
+ // If the closing tag matches, move back up to the parent node.
8309
+ if (tagStack.length && tagStack[tagStack.length - 1] === t.substr(2).replace(">", "")) {
8310
+ tagStack.pop();
8311
+ current = current.parentNode;
8312
+ }
8313
+ // Otherwise just ignore the end tag.
8314
+ continue;
8315
+ }
8316
+ var ts = parseTimeStamp(t.substr(1, t.length - 2));
8317
+ var node;
8318
+ if (ts) {
8319
+ // Timestamps are lead nodes as well.
8320
+ node = window.document.createProcessingInstruction("timestamp", ts);
8321
+ current.appendChild(node);
8322
+ continue;
8323
+ }
8324
+ var m = t.match(/^<([^.\s/0-9>]+)(\.[^\s\\>]+)?([^>\\]+)?(\\?)>?$/);
8325
+ // If we can't parse the tag, skip to the next tag.
8326
+ if (!m) {
8327
+ continue;
8328
+ }
8329
+ // Try to construct an element, and ignore the tag if we couldn't.
8330
+ node = createElement(m[1], m[3]);
8331
+ if (!node) {
8332
+ continue;
8333
+ }
8334
+ // Determine if the tag should be added based on the context of where it
8335
+ // is placed in the cuetext.
8336
+ if (!shouldAdd(current, node)) {
8337
+ continue;
8338
+ }
8339
+ // Set the class list (as a list of classes, separated by space).
8340
+ if (m[2]) {
8341
+ node.className = m[2].substr(1).replace('.', ' ');
8342
+ }
8343
+ // Append the node to the current node, and enter the scope of the new
8344
+ // node.
8345
+ tagStack.push(m[1]);
8346
+ current.appendChild(node);
8347
+ current = node;
8348
+ continue;
8349
+ }
8350
+
8351
+ // Text nodes are leaf nodes.
8352
+ current.appendChild(window.document.createTextNode(unescape(t)));
8353
+ }
8354
+
8355
+ return rootDiv;
8356
+ }
8357
+
8358
+ // This is a list of all the Unicode characters that have a strong
8359
+ // right-to-left category. What this means is that these characters are
8360
+ // written right-to-left for sure. It was generated by pulling all the strong
8361
+ // right-to-left characters out of the Unicode data table. That table can
8362
+ // found at: http://www.unicode.org/Public/UNIDATA/UnicodeData.txt
8363
+ var strongRTLRanges = [[0x5be, 0x5be], [0x5c0, 0x5c0], [0x5c3, 0x5c3], [0x5c6, 0x5c6], [0x5d0, 0x5ea], [0x5f0, 0x5f4], [0x608, 0x608], [0x60b, 0x60b], [0x60d, 0x60d], [0x61b, 0x61b], [0x61e, 0x64a], [0x66d, 0x66f], [0x671, 0x6d5], [0x6e5, 0x6e6], [0x6ee, 0x6ef], [0x6fa, 0x70d], [0x70f, 0x710], [0x712, 0x72f], [0x74d, 0x7a5], [0x7b1, 0x7b1], [0x7c0, 0x7ea], [0x7f4, 0x7f5], [0x7fa, 0x7fa], [0x800, 0x815], [0x81a, 0x81a], [0x824, 0x824], [0x828, 0x828], [0x830, 0x83e], [0x840, 0x858], [0x85e, 0x85e], [0x8a0, 0x8a0], [0x8a2, 0x8ac], [0x200f, 0x200f], [0xfb1d, 0xfb1d], [0xfb1f, 0xfb28], [0xfb2a, 0xfb36], [0xfb38, 0xfb3c], [0xfb3e, 0xfb3e], [0xfb40, 0xfb41], [0xfb43, 0xfb44], [0xfb46, 0xfbc1], [0xfbd3, 0xfd3d], [0xfd50, 0xfd8f], [0xfd92, 0xfdc7], [0xfdf0, 0xfdfc], [0xfe70, 0xfe74], [0xfe76, 0xfefc], [0x10800, 0x10805], [0x10808, 0x10808], [0x1080a, 0x10835], [0x10837, 0x10838], [0x1083c, 0x1083c], [0x1083f, 0x10855], [0x10857, 0x1085f], [0x10900, 0x1091b], [0x10920, 0x10939], [0x1093f, 0x1093f], [0x10980, 0x109b7], [0x109be, 0x109bf], [0x10a00, 0x10a00], [0x10a10, 0x10a13], [0x10a15, 0x10a17], [0x10a19, 0x10a33], [0x10a40, 0x10a47], [0x10a50, 0x10a58], [0x10a60, 0x10a7f], [0x10b00, 0x10b35], [0x10b40, 0x10b55], [0x10b58, 0x10b72], [0x10b78, 0x10b7f], [0x10c00, 0x10c48], [0x1ee00, 0x1ee03], [0x1ee05, 0x1ee1f], [0x1ee21, 0x1ee22], [0x1ee24, 0x1ee24], [0x1ee27, 0x1ee27], [0x1ee29, 0x1ee32], [0x1ee34, 0x1ee37], [0x1ee39, 0x1ee39], [0x1ee3b, 0x1ee3b], [0x1ee42, 0x1ee42], [0x1ee47, 0x1ee47], [0x1ee49, 0x1ee49], [0x1ee4b, 0x1ee4b], [0x1ee4d, 0x1ee4f], [0x1ee51, 0x1ee52], [0x1ee54, 0x1ee54], [0x1ee57, 0x1ee57], [0x1ee59, 0x1ee59], [0x1ee5b, 0x1ee5b], [0x1ee5d, 0x1ee5d], [0x1ee5f, 0x1ee5f], [0x1ee61, 0x1ee62], [0x1ee64, 0x1ee64], [0x1ee67, 0x1ee6a], [0x1ee6c, 0x1ee72], [0x1ee74, 0x1ee77], [0x1ee79, 0x1ee7c], [0x1ee7e, 0x1ee7e], [0x1ee80, 0x1ee89], [0x1ee8b, 0x1ee9b], [0x1eea1, 0x1eea3], [0x1eea5, 0x1eea9], [0x1eeab, 0x1eebb], [0x10fffd, 0x10fffd]];
8364
+
8365
+ function isStrongRTLChar(charCode) {
8366
+ for (var i = 0; i < strongRTLRanges.length; i++) {
8367
+ var currentRange = strongRTLRanges[i];
8368
+ if (charCode >= currentRange[0] && charCode <= currentRange[1]) {
8369
+ return true;
8370
+ }
8371
+ }
8372
+
8373
+ return false;
8374
+ }
8375
+
8376
+ function determineBidi(cueDiv) {
8377
+ var nodeStack = [],
8378
+ text = "",
8379
+ charCode;
8380
+
8381
+ if (!cueDiv || !cueDiv.childNodes) {
8382
+ return "ltr";
8383
+ }
8384
+
8385
+ function pushNodes(nodeStack, node) {
8386
+ for (var i = node.childNodes.length - 1; i >= 0; i--) {
8387
+ nodeStack.push(node.childNodes[i]);
8388
+ }
8389
+ }
8390
+
8391
+ function nextTextNode(nodeStack) {
8392
+ if (!nodeStack || !nodeStack.length) {
8393
+ return null;
8394
+ }
8395
+
8396
+ var node = nodeStack.pop(),
8397
+ text = node.textContent || node.innerText;
8398
+ if (text) {
8399
+ // TODO: This should match all unicode type B characters (paragraph
8400
+ // separator characters). See issue #115.
8401
+ var m = text.match(/^.*(\n|\r)/);
8402
+ if (m) {
8403
+ nodeStack.length = 0;
8404
+ return m[0];
8405
+ }
8406
+ return text;
8407
+ }
8408
+ if (node.tagName === "ruby") {
8409
+ return nextTextNode(nodeStack);
8410
+ }
8411
+ if (node.childNodes) {
8412
+ pushNodes(nodeStack, node);
8413
+ return nextTextNode(nodeStack);
8414
+ }
8415
+ }
8416
+
8417
+ pushNodes(nodeStack, cueDiv);
8418
+ while (text = nextTextNode(nodeStack)) {
8419
+ for (var i = 0; i < text.length; i++) {
8420
+ charCode = text.charCodeAt(i);
8421
+ if (isStrongRTLChar(charCode)) {
8422
+ return "rtl";
8423
+ }
8424
+ }
8425
+ }
8426
+ return "ltr";
8427
+ }
8428
+
8429
+ function computeLinePos(cue) {
8430
+ if (typeof cue.line === "number" && (cue.snapToLines || cue.line >= 0 && cue.line <= 100)) {
8431
+ return cue.line;
8432
+ }
8433
+ if (!cue.track || !cue.track.textTrackList || !cue.track.textTrackList.mediaElement) {
8434
+ return -1;
8435
+ }
8436
+ var track = cue.track,
8437
+ trackList = track.textTrackList,
8438
+ count = 0;
8439
+ for (var i = 0; i < trackList.length && trackList[i] !== track; i++) {
8440
+ if (trackList[i].mode === "showing") {
8441
+ count++;
8442
+ }
8443
+ }
8444
+ return ++count * -1;
8445
+ }
8446
+
8447
+ function StyleBox() {}
8448
+
8449
+ // Apply styles to a div. If there is no div passed then it defaults to the
8450
+ // div on 'this'.
8451
+ StyleBox.prototype.applyStyles = function (styles, div) {
8452
+ div = div || this.div;
8453
+ for (var prop in styles) {
8454
+ if (styles.hasOwnProperty(prop)) {
8455
+ div.style[prop] = styles[prop];
8456
+ }
8457
+ }
8458
+ };
8459
+
8460
+ StyleBox.prototype.formatStyle = function (val, unit) {
8461
+ return val === 0 ? 0 : val + unit;
8462
+ };
8463
+
8464
+ // Constructs the computed display state of the cue (a div). Places the div
8465
+ // into the overlay which should be a block level element (usually a div).
8466
+ function CueStyleBox(window, cue, styleOptions) {
8467
+ StyleBox.call(this);
8468
+ this.cue = cue;
8469
+
8470
+ // Parse our cue's text into a DOM tree rooted at 'cueDiv'. This div will
8471
+ // have inline positioning and will function as the cue background box.
8472
+ this.cueDiv = parseContent(window, cue.text);
8473
+ var styles = {
8474
+ color: "rgba(255, 255, 255, 1)",
8475
+ backgroundColor: "rgba(0, 0, 0, 0.8)",
8476
+ position: "relative",
8477
+ left: 0,
8478
+ right: 0,
8479
+ top: 0,
8480
+ bottom: 0,
8481
+ display: "inline",
8482
+ writingMode: cue.vertical === "" ? "horizontal-tb" : cue.vertical === "lr" ? "vertical-lr" : "vertical-rl",
8483
+ unicodeBidi: "plaintext"
8484
+ };
8485
+
8486
+ this.applyStyles(styles, this.cueDiv);
8487
+
8488
+ // Create an absolutely positioned div that will be used to position the cue
8489
+ // div. Note, all WebVTT cue-setting alignments are equivalent to the CSS
8490
+ // mirrors of them except "middle" which is "center" in CSS.
8491
+ this.div = window.document.createElement("div");
8492
+ styles = {
8493
+ direction: determineBidi(this.cueDiv),
8494
+ writingMode: cue.vertical === "" ? "horizontal-tb" : cue.vertical === "lr" ? "vertical-lr" : "vertical-rl",
8495
+ unicodeBidi: "plaintext",
8496
+ textAlign: cue.align === "middle" ? "center" : cue.align,
8497
+ font: styleOptions.font,
8498
+ whiteSpace: "pre-line",
8499
+ position: "absolute"
8500
+ };
8501
+
8502
+ this.applyStyles(styles);
8503
+ this.div.appendChild(this.cueDiv);
8504
+
8505
+ // Calculate the distance from the reference edge of the viewport to the text
8506
+ // position of the cue box. The reference edge will be resolved later when
8507
+ // the box orientation styles are applied.
8508
+ var textPos = 0;
8509
+ switch (cue.positionAlign) {
8510
+ case "start":
8511
+ textPos = cue.position;
8512
+ break;
8513
+ case "middle":
8514
+ textPos = cue.position - cue.size / 2;
8515
+ break;
8516
+ case "end":
8517
+ textPos = cue.position - cue.size;
8518
+ break;
8519
+ }
8520
+
8521
+ // Horizontal box orientation; textPos is the distance from the left edge of the
8522
+ // area to the left edge of the box and cue.size is the distance extending to
8523
+ // the right from there.
8524
+ if (cue.vertical === "") {
8525
+ this.applyStyles({
8526
+ left: this.formatStyle(textPos, "%"),
8527
+ width: this.formatStyle(cue.size, "%")
8528
+ });
8529
+ // Vertical box orientation; textPos is the distance from the top edge of the
8530
+ // area to the top edge of the box and cue.size is the height extending
8531
+ // downwards from there.
8532
+ } else {
8533
+ this.applyStyles({
8534
+ top: this.formatStyle(textPos, "%"),
8535
+ height: this.formatStyle(cue.size, "%")
8536
+ });
8537
+ }
8538
+
8539
+ this.move = function (box) {
8540
+ this.applyStyles({
8541
+ top: this.formatStyle(box.top, "px"),
8542
+ bottom: this.formatStyle(box.bottom, "px"),
8543
+ left: this.formatStyle(box.left, "px"),
8544
+ right: this.formatStyle(box.right, "px"),
8545
+ height: this.formatStyle(box.height, "px"),
8546
+ width: this.formatStyle(box.width, "px")
8547
+ });
8548
+ };
8549
+ }
8550
+ CueStyleBox.prototype = _objCreate(StyleBox.prototype);
8551
+ CueStyleBox.prototype.constructor = CueStyleBox;
8552
+
8553
+ // Represents the co-ordinates of an Element in a way that we can easily
8554
+ // compute things with such as if it overlaps or intersects with another Element.
8555
+ // Can initialize it with either a StyleBox or another BoxPosition.
8556
+ function BoxPosition(obj) {
8557
+ // Either a BoxPosition was passed in and we need to copy it, or a StyleBox
8558
+ // was passed in and we need to copy the results of 'getBoundingClientRect'
8559
+ // as the object returned is readonly. All co-ordinate values are in reference
8560
+ // to the viewport origin (top left).
8561
+ var lh, height, width, top;
8562
+ if (obj.div) {
8563
+ height = obj.div.offsetHeight;
8564
+ width = obj.div.offsetWidth;
8565
+ top = obj.div.offsetTop;
8566
+
8567
+ var rects = (rects = obj.div.childNodes) && (rects = rects[0]) && rects.getClientRects && rects.getClientRects();
8568
+ obj = obj.div.getBoundingClientRect();
8569
+ // In certain cases the outter div will be slightly larger then the sum of
8570
+ // the inner div's lines. This could be due to bold text, etc, on some platforms.
8571
+ // In this case we should get the average line height and use that. This will
8572
+ // result in the desired behaviour.
8573
+ lh = rects ? Math.max(rects[0] && rects[0].height || 0, obj.height / rects.length) : 0;
8574
+ }
8575
+ this.left = obj.left;
8576
+ this.right = obj.right;
8577
+ this.top = obj.top || top;
8578
+ this.height = obj.height || height;
8579
+ this.bottom = obj.bottom || top + (obj.height || height);
8580
+ this.width = obj.width || width;
8581
+ this.lineHeight = lh !== undefined ? lh : obj.lineHeight;
8582
+ }
8583
+
8584
+ // Move the box along a particular axis. Optionally pass in an amount to move
8585
+ // the box. If no amount is passed then the default is the line height of the
8586
+ // box.
8587
+ BoxPosition.prototype.move = function (axis, toMove) {
8588
+ toMove = toMove !== undefined ? toMove : this.lineHeight;
8589
+ switch (axis) {
8590
+ case "+x":
8591
+ this.left += toMove;
8592
+ this.right += toMove;
8593
+ break;
8594
+ case "-x":
8595
+ this.left -= toMove;
8596
+ this.right -= toMove;
8597
+ break;
8598
+ case "+y":
8599
+ this.top += toMove;
8600
+ this.bottom += toMove;
8601
+ break;
8602
+ case "-y":
8603
+ this.top -= toMove;
8604
+ this.bottom -= toMove;
8605
+ break;
8606
+ }
8607
+ };
8608
+
8609
+ // Check if this box overlaps another box, b2.
8610
+ BoxPosition.prototype.overlaps = function (b2) {
8611
+ return this.left < b2.right && this.right > b2.left && this.top < b2.bottom && this.bottom > b2.top;
8612
+ };
8613
+
8614
+ // Check if this box overlaps any other boxes in boxes.
8615
+ BoxPosition.prototype.overlapsAny = function (boxes) {
8616
+ for (var i = 0; i < boxes.length; i++) {
8617
+ if (this.overlaps(boxes[i])) {
8618
+ return true;
8619
+ }
8620
+ }
8621
+ return false;
8622
+ };
8623
+
8624
+ // Check if this box is within another box.
8625
+ BoxPosition.prototype.within = function (container) {
8626
+ return this.top >= container.top && this.bottom <= container.bottom && this.left >= container.left && this.right <= container.right;
8627
+ };
8628
+
8629
+ // Check if this box is entirely within the container or it is overlapping
8630
+ // on the edge opposite of the axis direction passed. For example, if "+x" is
8631
+ // passed and the box is overlapping on the left edge of the container, then
8632
+ // return true.
8633
+ BoxPosition.prototype.overlapsOppositeAxis = function (container, axis) {
8634
+ switch (axis) {
8635
+ case "+x":
8636
+ return this.left < container.left;
8637
+ case "-x":
8638
+ return this.right > container.right;
8639
+ case "+y":
8640
+ return this.top < container.top;
8641
+ case "-y":
8642
+ return this.bottom > container.bottom;
8643
+ }
8644
+ };
8645
+
8646
+ // Find the percentage of the area that this box is overlapping with another
8647
+ // box.
8648
+ BoxPosition.prototype.intersectPercentage = function (b2) {
8649
+ var x = Math.max(0, Math.min(this.right, b2.right) - Math.max(this.left, b2.left)),
8650
+ y = Math.max(0, Math.min(this.bottom, b2.bottom) - Math.max(this.top, b2.top)),
8651
+ intersectArea = x * y;
8652
+ return intersectArea / (this.height * this.width);
8653
+ };
8654
+
8655
+ // Convert the positions from this box to CSS compatible positions using
8656
+ // the reference container's positions. This has to be done because this
8657
+ // box's positions are in reference to the viewport origin, whereas, CSS
8658
+ // values are in referecne to their respective edges.
8659
+ BoxPosition.prototype.toCSSCompatValues = function (reference) {
8660
+ return {
8661
+ top: this.top - reference.top,
8662
+ bottom: reference.bottom - this.bottom,
8663
+ left: this.left - reference.left,
8664
+ right: reference.right - this.right,
8665
+ height: this.height,
8666
+ width: this.width
8667
+ };
8668
+ };
8669
+
8670
+ // Get an object that represents the box's position without anything extra.
8671
+ // Can pass a StyleBox, HTMLElement, or another BoxPositon.
8672
+ BoxPosition.getSimpleBoxPosition = function (obj) {
8673
+ var height = obj.div ? obj.div.offsetHeight : obj.tagName ? obj.offsetHeight : 0;
8674
+ var width = obj.div ? obj.div.offsetWidth : obj.tagName ? obj.offsetWidth : 0;
8675
+ var top = obj.div ? obj.div.offsetTop : obj.tagName ? obj.offsetTop : 0;
8676
+
8677
+ obj = obj.div ? obj.div.getBoundingClientRect() : obj.tagName ? obj.getBoundingClientRect() : obj;
8678
+ var ret = {
8679
+ left: obj.left,
8680
+ right: obj.right,
8681
+ top: obj.top || top,
8682
+ height: obj.height || height,
8683
+ bottom: obj.bottom || top + (obj.height || height),
8684
+ width: obj.width || width
8685
+ };
8686
+ return ret;
8687
+ };
8688
+
8689
+ // Move a StyleBox to its specified, or next best, position. The containerBox
8690
+ // is the box that contains the StyleBox, such as a div. boxPositions are
8691
+ // a list of other boxes that the styleBox can't overlap with.
8692
+ function moveBoxToLinePosition(window, styleBox, containerBox, boxPositions) {
8693
+
8694
+ // Find the best position for a cue box, b, on the video. The axis parameter
8695
+ // is a list of axis, the order of which, it will move the box along. For example:
8696
+ // Passing ["+x", "-x"] will move the box first along the x axis in the positive
8697
+ // direction. If it doesn't find a good position for it there it will then move
8698
+ // it along the x axis in the negative direction.
8699
+ function findBestPosition(b, axis) {
8700
+ var bestPosition,
8701
+ specifiedPosition = new BoxPosition(b),
8702
+ percentage = 1; // Highest possible so the first thing we get is better.
8703
+
8704
+ for (var i = 0; i < axis.length; i++) {
8705
+ while (b.overlapsOppositeAxis(containerBox, axis[i]) || b.within(containerBox) && b.overlapsAny(boxPositions)) {
8706
+ b.move(axis[i]);
8707
+ }
8708
+ // We found a spot where we aren't overlapping anything. This is our
8709
+ // best position.
8710
+ if (b.within(containerBox)) {
8711
+ return b;
8712
+ }
8713
+ var p = b.intersectPercentage(containerBox);
8714
+ // If we're outside the container box less then we were on our last try
8715
+ // then remember this position as the best position.
8716
+ if (percentage > p) {
8717
+ bestPosition = new BoxPosition(b);
8718
+ percentage = p;
8719
+ }
8720
+ // Reset the box position to the specified position.
8721
+ b = new BoxPosition(specifiedPosition);
8722
+ }
8723
+ return bestPosition || specifiedPosition;
8724
+ }
8725
+
8726
+ var boxPosition = new BoxPosition(styleBox),
8727
+ cue = styleBox.cue,
8728
+ linePos = computeLinePos(cue),
8729
+ axis = [];
8730
+
8731
+ // If we have a line number to align the cue to.
8732
+ if (cue.snapToLines) {
8733
+ var size;
8734
+ switch (cue.vertical) {
8735
+ case "":
8736
+ axis = ["+y", "-y"];
8737
+ size = "height";
8738
+ break;
8739
+ case "rl":
8740
+ axis = ["+x", "-x"];
8741
+ size = "width";
8742
+ break;
8743
+ case "lr":
8744
+ axis = ["-x", "+x"];
8745
+ size = "width";
8746
+ break;
8747
+ }
8748
+
8749
+ var step = boxPosition.lineHeight,
8750
+ position = step * Math.round(linePos),
8751
+ maxPosition = containerBox[size] + step,
8752
+ initialAxis = axis[0];
8753
+
8754
+ // If the specified intial position is greater then the max position then
8755
+ // clamp the box to the amount of steps it would take for the box to
8756
+ // reach the max position.
8757
+ if (Math.abs(position) > maxPosition) {
8758
+ position = position < 0 ? -1 : 1;
8759
+ position *= Math.ceil(maxPosition / step) * step;
8760
+ }
8761
+
8762
+ // If computed line position returns negative then line numbers are
8763
+ // relative to the bottom of the video instead of the top. Therefore, we
8764
+ // need to increase our initial position by the length or width of the
8765
+ // video, depending on the writing direction, and reverse our axis directions.
8766
+ if (linePos < 0) {
8767
+ position += cue.vertical === "" ? containerBox.height : containerBox.width;
8768
+ axis = axis.reverse();
8769
+ }
8770
+
8771
+ // Move the box to the specified position. This may not be its best
8772
+ // position.
8773
+ boxPosition.move(initialAxis, position);
8774
+ } else {
8775
+ // If we have a percentage line value for the cue.
8776
+ var calculatedPercentage = boxPosition.lineHeight / containerBox.height * 100;
8777
+
8778
+ switch (cue.lineAlign) {
8779
+ case "middle":
8780
+ linePos -= calculatedPercentage / 2;
8781
+ break;
8782
+ case "end":
8783
+ linePos -= calculatedPercentage;
8784
+ break;
8785
+ }
8786
+
8787
+ // Apply initial line position to the cue box.
8788
+ switch (cue.vertical) {
8789
+ case "":
8790
+ styleBox.applyStyles({
8791
+ top: styleBox.formatStyle(linePos, "%")
8792
+ });
8793
+ break;
8794
+ case "rl":
8795
+ styleBox.applyStyles({
8796
+ left: styleBox.formatStyle(linePos, "%")
8797
+ });
8798
+ break;
8799
+ case "lr":
8800
+ styleBox.applyStyles({
8801
+ right: styleBox.formatStyle(linePos, "%")
8802
+ });
8803
+ break;
8804
+ }
8805
+
8806
+ axis = ["+y", "-x", "+x", "-y"];
8807
+
8808
+ // Get the box position again after we've applied the specified positioning
8809
+ // to it.
8810
+ boxPosition = new BoxPosition(styleBox);
8811
+ }
8812
+
8813
+ var bestPosition = findBestPosition(boxPosition, axis);
8814
+ styleBox.move(bestPosition.toCSSCompatValues(containerBox));
8815
+ }
8816
+
8817
+ function WebVTT$1() {}
8818
+ // Nothing
8819
+
8820
+
8821
+ // Helper to allow strings to be decoded instead of the default binary utf8 data.
8822
+ WebVTT$1.StringDecoder = function () {
8823
+ return {
8824
+ decode: function decode(data) {
8825
+ if (!data) {
8826
+ return "";
8827
+ }
8828
+ if (typeof data !== "string") {
8829
+ throw new Error("Error - expected string data.");
8830
+ }
8831
+ return decodeURIComponent(encodeURIComponent(data));
8832
+ }
8833
+ };
8834
+ };
8835
+
8836
+ WebVTT$1.convertCueToDOMTree = function (window, cuetext) {
8837
+ if (!window || !cuetext) {
8838
+ return null;
8839
+ }
8840
+ return parseContent(window, cuetext);
8841
+ };
8842
+
8843
+ var FONT_SIZE_PERCENT = 0.05;
8844
+ var FONT_STYLE = "sans-serif";
8845
+ var CUE_BACKGROUND_PADDING = "1.5%";
8846
+
8847
+ // Runs the processing model over the cues and regions passed to it.
8848
+ // @param overlay A block level element (usually a div) that the computed cues
8849
+ // and regions will be placed into.
8850
+ WebVTT$1.processCues = function (window, cues, overlay) {
8851
+ if (!window || !cues || !overlay) {
8852
+ return null;
8853
+ }
8854
+
8855
+ // Remove all previous children.
8856
+ while (overlay.firstChild) {
8857
+ overlay.removeChild(overlay.firstChild);
8858
+ }
8859
+
8860
+ var paddedOverlay = window.document.createElement("div");
8861
+ paddedOverlay.style.position = "absolute";
8862
+ paddedOverlay.style.left = "0";
8863
+ paddedOverlay.style.right = "0";
8864
+ paddedOverlay.style.top = "0";
8865
+ paddedOverlay.style.bottom = "0";
8866
+ paddedOverlay.style.margin = CUE_BACKGROUND_PADDING;
8867
+ overlay.appendChild(paddedOverlay);
8868
+
8869
+ // Determine if we need to compute the display states of the cues. This could
8870
+ // be the case if a cue's state has been changed since the last computation or
8871
+ // if it has not been computed yet.
8872
+ function shouldCompute(cues) {
8873
+ for (var i = 0; i < cues.length; i++) {
8874
+ if (cues[i].hasBeenReset || !cues[i].displayState) {
8875
+ return true;
8876
+ }
8877
+ }
8878
+ return false;
8879
+ }
8880
+
8881
+ // We don't need to recompute the cues' display states. Just reuse them.
8882
+ if (!shouldCompute(cues)) {
8883
+ for (var i = 0; i < cues.length; i++) {
8884
+ paddedOverlay.appendChild(cues[i].displayState);
8885
+ }
8886
+ return;
8887
+ }
8888
+
8889
+ var boxPositions = [],
8890
+ containerBox = BoxPosition.getSimpleBoxPosition(paddedOverlay),
8891
+ fontSize = Math.round(containerBox.height * FONT_SIZE_PERCENT * 100) / 100;
8892
+ var styleOptions = {
8893
+ font: fontSize + "px " + FONT_STYLE
8894
+ };
8895
+
8896
+ (function () {
8897
+ var styleBox, cue;
8898
+
8899
+ for (var i = 0; i < cues.length; i++) {
8900
+ cue = cues[i];
8901
+
8902
+ // Compute the intial position and styles of the cue div.
8903
+ styleBox = new CueStyleBox(window, cue, styleOptions);
8904
+ paddedOverlay.appendChild(styleBox.div);
8905
+
8906
+ // Move the cue div to it's correct line position.
8907
+ moveBoxToLinePosition(window, styleBox, containerBox, boxPositions);
8908
+
8909
+ // Remember the computed div so that we don't have to recompute it later
8910
+ // if we don't have too.
8911
+ cue.displayState = styleBox.div;
8912
+
8913
+ boxPositions.push(BoxPosition.getSimpleBoxPosition(styleBox));
8914
+ }
8915
+ })();
8916
+ };
8917
+
8918
+ WebVTT$1.Parser = function (window, vttjs, decoder) {
8919
+ if (!decoder) {
8920
+ decoder = vttjs;
8921
+ vttjs = {};
8922
+ }
8923
+ if (!vttjs) {
8924
+ vttjs = {};
8925
+ }
8926
+
8927
+ this.window = window;
8928
+ this.vttjs = vttjs;
8929
+ this.state = "INITIAL";
8930
+ this.buffer = "";
8931
+ this.decoder = decoder || new TextDecoder("utf8");
8932
+ this.regionList = [];
8933
+ };
8934
+
8935
+ WebVTT$1.Parser.prototype = {
8936
+ // If the error is a ParsingError then report it to the consumer if
8937
+ // possible. If it's not a ParsingError then throw it like normal.
8938
+ reportOrThrowError: function reportOrThrowError(e) {
8939
+ if (e instanceof ParsingError) {
8940
+ this.onparsingerror && this.onparsingerror(e);
8941
+ } else {
8942
+ throw e;
8943
+ }
8944
+ },
8945
+ parse: function parse(data) {
8946
+ var self = this;
8947
+
8948
+ // If there is no data then we won't decode it, but will just try to parse
8949
+ // whatever is in buffer already. This may occur in circumstances, for
8950
+ // example when flush() is called.
8951
+ if (data) {
8952
+ // Try to decode the data that we received.
8953
+ self.buffer += self.decoder.decode(data, { stream: true });
8954
+ }
8955
+
8956
+ function collectNextLine() {
8957
+ var buffer = self.buffer;
8958
+ var pos = 0;
8959
+ while (pos < buffer.length && buffer[pos] !== '\r' && buffer[pos] !== '\n') {
8960
+ ++pos;
8961
+ }
8962
+ var line = buffer.substr(0, pos);
8963
+ // Advance the buffer early in case we fail below.
8964
+ if (buffer[pos] === '\r') {
8965
+ ++pos;
8966
+ }
8967
+ if (buffer[pos] === '\n') {
8968
+ ++pos;
8969
+ }
8970
+ self.buffer = buffer.substr(pos);
8971
+ return line;
8972
+ }
8973
+
8974
+ // 3.4 WebVTT region and WebVTT region settings syntax
8975
+ function parseRegion(input) {
8976
+ var settings = new Settings();
8977
+
8978
+ parseOptions(input, function (k, v) {
8979
+ switch (k) {
8980
+ case "id":
8981
+ settings.set(k, v);
8982
+ break;
8983
+ case "width":
8984
+ settings.percent(k, v);
8985
+ break;
8986
+ case "lines":
8987
+ settings.integer(k, v);
8988
+ break;
8989
+ case "regionanchor":
8990
+ case "viewportanchor":
8991
+ var xy = v.split(',');
8992
+ if (xy.length !== 2) {
8993
+ break;
8994
+ }
8995
+ // We have to make sure both x and y parse, so use a temporary
8996
+ // settings object here.
8997
+ var anchor = new Settings();
8998
+ anchor.percent("x", xy[0]);
8999
+ anchor.percent("y", xy[1]);
9000
+ if (!anchor.has("x") || !anchor.has("y")) {
9001
+ break;
9002
+ }
9003
+ settings.set(k + "X", anchor.get("x"));
9004
+ settings.set(k + "Y", anchor.get("y"));
9005
+ break;
9006
+ case "scroll":
9007
+ settings.alt(k, v, ["up"]);
9008
+ break;
9009
+ }
9010
+ }, /=/, /\s/);
9011
+
9012
+ // Create the region, using default values for any values that were not
9013
+ // specified.
9014
+ if (settings.has("id")) {
9015
+ var region = new (self.vttjs.VTTRegion || self.window.VTTRegion)();
9016
+ region.width = settings.get("width", 100);
9017
+ region.lines = settings.get("lines", 3);
9018
+ region.regionAnchorX = settings.get("regionanchorX", 0);
9019
+ region.regionAnchorY = settings.get("regionanchorY", 100);
9020
+ region.viewportAnchorX = settings.get("viewportanchorX", 0);
9021
+ region.viewportAnchorY = settings.get("viewportanchorY", 100);
9022
+ region.scroll = settings.get("scroll", "");
9023
+ // Register the region.
9024
+ self.onregion && self.onregion(region);
9025
+ // Remember the VTTRegion for later in case we parse any VTTCues that
9026
+ // reference it.
9027
+ self.regionList.push({
9028
+ id: settings.get("id"),
9029
+ region: region
9030
+ });
9031
+ }
9032
+ }
9033
+
9034
+ // draft-pantos-http-live-streaming-20
9035
+ // https://tools.ietf.org/html/draft-pantos-http-live-streaming-20#section-3.5
9036
+ // 3.5 WebVTT
9037
+ function parseTimestampMap(input) {
9038
+ var settings = new Settings();
9039
+
9040
+ parseOptions(input, function (k, v) {
9041
+ switch (k) {
9042
+ case "MPEGT":
9043
+ settings.integer(k + 'S', v);
9044
+ break;
9045
+ case "LOCA":
9046
+ settings.set(k + 'L', parseTimeStamp(v));
9047
+ break;
9048
+ }
9049
+ }, /[^\d]:/, /,/);
9050
+
9051
+ self.ontimestampmap && self.ontimestampmap({
9052
+ "MPEGTS": settings.get("MPEGTS"),
9053
+ "LOCAL": settings.get("LOCAL")
9054
+ });
9055
+ }
9056
+
9057
+ // 3.2 WebVTT metadata header syntax
9058
+ function parseHeader(input) {
9059
+ if (input.match(/X-TIMESTAMP-MAP/)) {
9060
+ // This line contains HLS X-TIMESTAMP-MAP metadata
9061
+ parseOptions(input, function (k, v) {
9062
+ switch (k) {
9063
+ case "X-TIMESTAMP-MAP":
9064
+ parseTimestampMap(v);
9065
+ break;
9066
+ }
9067
+ }, /=/);
9068
+ } else {
9069
+ parseOptions(input, function (k, v) {
9070
+ switch (k) {
9071
+ case "Region":
9072
+ // 3.3 WebVTT region metadata header syntax
9073
+ parseRegion(v);
9074
+ break;
9075
+ }
9076
+ }, /:/);
9077
+ }
9078
+ }
9079
+
9080
+ // 5.1 WebVTT file parsing.
9081
+ try {
9082
+ var line;
9083
+ if (self.state === "INITIAL") {
9084
+ // We can't start parsing until we have the first line.
9085
+ if (!/\r\n|\n/.test(self.buffer)) {
9086
+ return this;
9087
+ }
9088
+
9089
+ line = collectNextLine();
9090
+
9091
+ var m = line.match(/^WEBVTT([ \t].*)?$/);
9092
+ if (!m || !m[0]) {
9093
+ throw new ParsingError(ParsingError.Errors.BadSignature);
9094
+ }
9095
+
9096
+ self.state = "HEADER";
9097
+ }
9098
+
9099
+ var alreadyCollectedLine = false;
9100
+ while (self.buffer) {
9101
+ // We can't parse a line until we have the full line.
9102
+ if (!/\r\n|\n/.test(self.buffer)) {
9103
+ return this;
9104
+ }
9105
+
9106
+ if (!alreadyCollectedLine) {
9107
+ line = collectNextLine();
9108
+ } else {
9109
+ alreadyCollectedLine = false;
9110
+ }
9111
+
9112
+ switch (self.state) {
9113
+ case "HEADER":
9114
+ // 13-18 - Allow a header (metadata) under the WEBVTT line.
9115
+ if (/:/.test(line)) {
9116
+ parseHeader(line);
9117
+ } else if (!line) {
9118
+ // An empty line terminates the header and starts the body (cues).
9119
+ self.state = "ID";
9120
+ }
9121
+ continue;
9122
+ case "NOTE":
9123
+ // Ignore NOTE blocks.
9124
+ if (!line) {
9125
+ self.state = "ID";
9126
+ }
9127
+ continue;
9128
+ case "ID":
9129
+ // Check for the start of NOTE blocks.
9130
+ if (/^NOTE($|[ \t])/.test(line)) {
9131
+ self.state = "NOTE";
9132
+ break;
9133
+ }
9134
+ // 19-29 - Allow any number of line terminators, then initialize new cue values.
9135
+ if (!line) {
9136
+ continue;
9137
+ }
9138
+ self.cue = new (self.vttjs.VTTCue || self.window.VTTCue)(0, 0, "");
9139
+ self.state = "CUE";
9140
+ // 30-39 - Check if self line contains an optional identifier or timing data.
9141
+ if (line.indexOf("-->") === -1) {
9142
+ self.cue.id = line;
9143
+ continue;
9144
+ }
9145
+ // Process line as start of a cue.
9146
+ /*falls through*/
9147
+ case "CUE":
9148
+ // 40 - Collect cue timings and settings.
9149
+ try {
9150
+ parseCue(line, self.cue, self.regionList);
9151
+ } catch (e) {
9152
+ self.reportOrThrowError(e);
9153
+ // In case of an error ignore rest of the cue.
9154
+ self.cue = null;
9155
+ self.state = "BADCUE";
9156
+ continue;
9157
+ }
9158
+ self.state = "CUETEXT";
9159
+ continue;
9160
+ case "CUETEXT":
9161
+ var hasSubstring = line.indexOf("-->") !== -1;
9162
+ // 34 - If we have an empty line then report the cue.
9163
+ // 35 - If we have the special substring '-->' then report the cue,
9164
+ // but do not collect the line as we need to process the current
9165
+ // one as a new cue.
9166
+ if (!line || hasSubstring && (alreadyCollectedLine = true)) {
9167
+ // We are done parsing self cue.
9168
+ self.oncue && self.oncue(self.cue);
9169
+ self.cue = null;
9170
+ self.state = "ID";
9171
+ continue;
9172
+ }
9173
+ if (self.cue.text) {
9174
+ self.cue.text += "\n";
9175
+ }
9176
+ self.cue.text += line;
9177
+ continue;
9178
+ case "BADCUE":
9179
+ // BADCUE
9180
+ // 54-62 - Collect and discard the remaining cue.
9181
+ if (!line) {
9182
+ self.state = "ID";
9183
+ }
9184
+ continue;
9185
+ }
9186
+ }
9187
+ } catch (e) {
9188
+ self.reportOrThrowError(e);
9189
+
9190
+ // If we are currently parsing a cue, report what we have.
9191
+ if (self.state === "CUETEXT" && self.cue && self.oncue) {
9192
+ self.oncue(self.cue);
9193
+ }
9194
+ self.cue = null;
9195
+ // Enter BADWEBVTT state if header was not parsed correctly otherwise
9196
+ // another exception occurred so enter BADCUE state.
9197
+ self.state = self.state === "INITIAL" ? "BADWEBVTT" : "BADCUE";
9198
+ }
9199
+ return this;
9200
+ },
9201
+ flush: function flush() {
9202
+ var self = this;
9203
+ try {
9204
+ // Finish decoding the stream.
9205
+ self.buffer += self.decoder.decode();
9206
+ // Synthesize the end of the current cue or region.
9207
+ if (self.cue || self.state === "HEADER") {
9208
+ self.buffer += "\n\n";
9209
+ self.parse();
9210
+ }
9211
+ // If we've flushed, parsed, and we're still on the INITIAL state then
9212
+ // that means we don't have enough of the stream to parse the first
9213
+ // line.
9214
+ if (self.state === "INITIAL") {
9215
+ throw new ParsingError(ParsingError.Errors.BadSignature);
9216
+ }
9217
+ } catch (e) {
9218
+ self.reportOrThrowError(e);
9219
+ }
9220
+ self.onflush && self.onflush();
9221
+ return this;
9222
+ }
9223
+ };
9224
+
9225
+ var vtt = WebVTT$1;
9226
+
9227
+ var vtt$1 = /*#__PURE__*/Object.freeze({
9228
+ default: vtt,
9229
+ __moduleExports: vtt
9230
+ });
9231
+
9232
+ /**
9233
+ * Copyright 2013 vtt.js Contributors
9234
+ *
9235
+ * Licensed under the Apache License, Version 2.0 (the "License");
9236
+ * you may not use this file except in compliance with the License.
9237
+ * You may obtain a copy of the License at
9238
+ *
9239
+ * http://www.apache.org/licenses/LICENSE-2.0
9240
+ *
9241
+ * Unless required by applicable law or agreed to in writing, software
9242
+ * distributed under the License is distributed on an "AS IS" BASIS,
9243
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9244
+ * See the License for the specific language governing permissions and
9245
+ * limitations under the License.
9246
+ */
9247
+
9248
+ var autoKeyword = "auto";
9249
+ var directionSetting = {
9250
+ "": 1,
9251
+ "lr": 1,
9252
+ "rl": 1
9253
+ };
9254
+ var alignSetting = {
9255
+ "start": 1,
9256
+ "middle": 1,
9257
+ "end": 1,
9258
+ "left": 1,
9259
+ "right": 1
9260
+ };
9261
+
9262
+ function findDirectionSetting(value) {
9263
+ if (typeof value !== "string") {
9264
+ return false;
9265
+ }
9266
+ var dir = directionSetting[value.toLowerCase()];
9267
+ return dir ? value.toLowerCase() : false;
9268
+ }
9269
+
9270
+ function findAlignSetting(value) {
9271
+ if (typeof value !== "string") {
9272
+ return false;
9273
+ }
9274
+ var align = alignSetting[value.toLowerCase()];
9275
+ return align ? value.toLowerCase() : false;
9276
+ }
9277
+
9278
+ function VTTCue(startTime, endTime, text) {
9279
+ /**
9280
+ * Shim implementation specific properties. These properties are not in
9281
+ * the spec.
9282
+ */
9283
+
9284
+ // Lets us know when the VTTCue's data has changed in such a way that we need
9285
+ // to recompute its display state. This lets us compute its display state
9286
+ // lazily.
9287
+ this.hasBeenReset = false;
9288
+
9289
+ /**
9290
+ * VTTCue and TextTrackCue properties
9291
+ * http://dev.w3.org/html5/webvtt/#vttcue-interface
9292
+ */
9293
+
9294
+ var _id = "";
9295
+ var _pauseOnExit = false;
9296
+ var _startTime = startTime;
9297
+ var _endTime = endTime;
9298
+ var _text = text;
9299
+ var _region = null;
9300
+ var _vertical = "";
9301
+ var _snapToLines = true;
9302
+ var _line = "auto";
9303
+ var _lineAlign = "start";
9304
+ var _position = 50;
9305
+ var _positionAlign = "middle";
9306
+ var _size = 50;
9307
+ var _align = "middle";
9308
+
9309
+ Object.defineProperties(this, {
9310
+ "id": {
9311
+ enumerable: true,
9312
+ get: function get() {
9313
+ return _id;
9314
+ },
9315
+ set: function set(value) {
9316
+ _id = "" + value;
9317
+ }
9318
+ },
9319
+
9320
+ "pauseOnExit": {
9321
+ enumerable: true,
9322
+ get: function get() {
9323
+ return _pauseOnExit;
9324
+ },
9325
+ set: function set(value) {
9326
+ _pauseOnExit = !!value;
9327
+ }
9328
+ },
9329
+
9330
+ "startTime": {
9331
+ enumerable: true,
9332
+ get: function get() {
9333
+ return _startTime;
9334
+ },
9335
+ set: function set(value) {
9336
+ if (typeof value !== "number") {
9337
+ throw new TypeError("Start time must be set to a number.");
9338
+ }
9339
+ _startTime = value;
9340
+ this.hasBeenReset = true;
9341
+ }
9342
+ },
9343
+
9344
+ "endTime": {
9345
+ enumerable: true,
9346
+ get: function get() {
9347
+ return _endTime;
9348
+ },
9349
+ set: function set(value) {
9350
+ if (typeof value !== "number") {
9351
+ throw new TypeError("End time must be set to a number.");
9352
+ }
9353
+ _endTime = value;
9354
+ this.hasBeenReset = true;
9355
+ }
9356
+ },
9357
+
9358
+ "text": {
9359
+ enumerable: true,
9360
+ get: function get() {
9361
+ return _text;
9362
+ },
9363
+ set: function set(value) {
9364
+ _text = "" + value;
9365
+ this.hasBeenReset = true;
9366
+ }
9367
+ },
9368
+
9369
+ "region": {
9370
+ enumerable: true,
9371
+ get: function get() {
9372
+ return _region;
9373
+ },
9374
+ set: function set(value) {
9375
+ _region = value;
9376
+ this.hasBeenReset = true;
9377
+ }
9378
+ },
9379
+
9380
+ "vertical": {
9381
+ enumerable: true,
9382
+ get: function get() {
9383
+ return _vertical;
9384
+ },
9385
+ set: function set(value) {
9386
+ var setting = findDirectionSetting(value);
9387
+ // Have to check for false because the setting an be an empty string.
9388
+ if (setting === false) {
9389
+ throw new SyntaxError("An invalid or illegal string was specified.");
9390
+ }
9391
+ _vertical = setting;
9392
+ this.hasBeenReset = true;
9393
+ }
9394
+ },
9395
+
9396
+ "snapToLines": {
9397
+ enumerable: true,
9398
+ get: function get() {
9399
+ return _snapToLines;
9400
+ },
9401
+ set: function set(value) {
9402
+ _snapToLines = !!value;
9403
+ this.hasBeenReset = true;
9404
+ }
9405
+ },
9406
+
9407
+ "line": {
9408
+ enumerable: true,
9409
+ get: function get() {
9410
+ return _line;
9411
+ },
9412
+ set: function set(value) {
9413
+ if (typeof value !== "number" && value !== autoKeyword) {
9414
+ throw new SyntaxError("An invalid number or illegal string was specified.");
9415
+ }
9416
+ _line = value;
9417
+ this.hasBeenReset = true;
9418
+ }
9419
+ },
9420
+
9421
+ "lineAlign": {
9422
+ enumerable: true,
9423
+ get: function get() {
9424
+ return _lineAlign;
9425
+ },
9426
+ set: function set(value) {
9427
+ var setting = findAlignSetting(value);
9428
+ if (!setting) {
9429
+ throw new SyntaxError("An invalid or illegal string was specified.");
9430
+ }
9431
+ _lineAlign = setting;
9432
+ this.hasBeenReset = true;
9433
+ }
9434
+ },
9435
+
9436
+ "position": {
9437
+ enumerable: true,
9438
+ get: function get() {
9439
+ return _position;
9440
+ },
9441
+ set: function set(value) {
9442
+ if (value < 0 || value > 100) {
9443
+ throw new Error("Position must be between 0 and 100.");
9444
+ }
9445
+ _position = value;
9446
+ this.hasBeenReset = true;
9447
+ }
9448
+ },
9449
+
9450
+ "positionAlign": {
9451
+ enumerable: true,
9452
+ get: function get() {
9453
+ return _positionAlign;
9454
+ },
9455
+ set: function set(value) {
9456
+ var setting = findAlignSetting(value);
9457
+ if (!setting) {
9458
+ throw new SyntaxError("An invalid or illegal string was specified.");
9459
+ }
9460
+ _positionAlign = setting;
9461
+ this.hasBeenReset = true;
9462
+ }
9463
+ },
9464
+
9465
+ "size": {
9466
+ enumerable: true,
9467
+ get: function get() {
9468
+ return _size;
9469
+ },
9470
+ set: function set(value) {
9471
+ if (value < 0 || value > 100) {
9472
+ throw new Error("Size must be between 0 and 100.");
9473
+ }
9474
+ _size = value;
9475
+ this.hasBeenReset = true;
9476
+ }
9477
+ },
9478
+
9479
+ "align": {
9480
+ enumerable: true,
9481
+ get: function get() {
9482
+ return _align;
9483
+ },
9484
+ set: function set(value) {
9485
+ var setting = findAlignSetting(value);
9486
+ if (!setting) {
9487
+ throw new SyntaxError("An invalid or illegal string was specified.");
9488
+ }
9489
+ _align = setting;
9490
+ this.hasBeenReset = true;
9491
+ }
9492
+ }
9493
+ });
9494
+
9495
+ /**
9496
+ * Other <track> spec defined properties
9497
+ */
9498
+
9499
+ // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-video-element.html#text-track-cue-display-state
9500
+ this.displayState = undefined;
9501
+ }
9502
+
9503
+ /**
9504
+ * VTTCue methods
9505
+ */
9506
+
9507
+ VTTCue.prototype.getCueAsHTML = function () {
9508
+ // Assume WebVTT.convertCueToDOMTree is on the global.
9509
+ return WebVTT.convertCueToDOMTree(window, this.text);
9510
+ };
9511
+
9512
+ var vttcue = VTTCue;
9513
+
9514
+ var vttcue$1 = /*#__PURE__*/Object.freeze({
9515
+ default: vttcue,
9516
+ __moduleExports: vttcue
9517
+ });
9518
+
9519
+ /**
9520
+ * Copyright 2013 vtt.js Contributors
9521
+ *
9522
+ * Licensed under the Apache License, Version 2.0 (the "License");
9523
+ * you may not use this file except in compliance with the License.
9524
+ * You may obtain a copy of the License at
9525
+ *
9526
+ * http://www.apache.org/licenses/LICENSE-2.0
9527
+ *
9528
+ * Unless required by applicable law or agreed to in writing, software
9529
+ * distributed under the License is distributed on an "AS IS" BASIS,
9530
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9531
+ * See the License for the specific language governing permissions and
9532
+ * limitations under the License.
9533
+ */
9534
+
9535
+ var scrollSetting = {
9536
+ "": true,
9537
+ "up": true
9538
+ };
9539
+
9540
+ function findScrollSetting(value) {
9541
+ if (typeof value !== "string") {
9542
+ return false;
9543
+ }
9544
+ var scroll = scrollSetting[value.toLowerCase()];
9545
+ return scroll ? value.toLowerCase() : false;
9546
+ }
9547
+
9548
+ function isValidPercentValue(value) {
9549
+ return typeof value === "number" && value >= 0 && value <= 100;
9550
+ }
9551
+
9552
+ // VTTRegion shim http://dev.w3.org/html5/webvtt/#vttregion-interface
9553
+ function VTTRegion() {
9554
+ var _width = 100;
9555
+ var _lines = 3;
9556
+ var _regionAnchorX = 0;
9557
+ var _regionAnchorY = 100;
9558
+ var _viewportAnchorX = 0;
9559
+ var _viewportAnchorY = 100;
9560
+ var _scroll = "";
9561
+
9562
+ Object.defineProperties(this, {
9563
+ "width": {
9564
+ enumerable: true,
9565
+ get: function get() {
9566
+ return _width;
9567
+ },
9568
+ set: function set(value) {
9569
+ if (!isValidPercentValue(value)) {
9570
+ throw new Error("Width must be between 0 and 100.");
9571
+ }
9572
+ _width = value;
9573
+ }
9574
+ },
9575
+ "lines": {
9576
+ enumerable: true,
9577
+ get: function get() {
9578
+ return _lines;
9579
+ },
9580
+ set: function set(value) {
9581
+ if (typeof value !== "number") {
9582
+ throw new TypeError("Lines must be set to a number.");
9583
+ }
9584
+ _lines = value;
9585
+ }
9586
+ },
9587
+ "regionAnchorY": {
9588
+ enumerable: true,
9589
+ get: function get() {
9590
+ return _regionAnchorY;
9591
+ },
9592
+ set: function set(value) {
9593
+ if (!isValidPercentValue(value)) {
9594
+ throw new Error("RegionAnchorX must be between 0 and 100.");
9595
+ }
9596
+ _regionAnchorY = value;
9597
+ }
9598
+ },
9599
+ "regionAnchorX": {
9600
+ enumerable: true,
9601
+ get: function get() {
9602
+ return _regionAnchorX;
9603
+ },
9604
+ set: function set(value) {
9605
+ if (!isValidPercentValue(value)) {
9606
+ throw new Error("RegionAnchorY must be between 0 and 100.");
9607
+ }
9608
+ _regionAnchorX = value;
9609
+ }
9610
+ },
9611
+ "viewportAnchorY": {
9612
+ enumerable: true,
9613
+ get: function get() {
9614
+ return _viewportAnchorY;
9615
+ },
9616
+ set: function set(value) {
9617
+ if (!isValidPercentValue(value)) {
9618
+ throw new Error("ViewportAnchorY must be between 0 and 100.");
9619
+ }
9620
+ _viewportAnchorY = value;
9621
+ }
9622
+ },
9623
+ "viewportAnchorX": {
9624
+ enumerable: true,
9625
+ get: function get() {
9626
+ return _viewportAnchorX;
9627
+ },
9628
+ set: function set(value) {
9629
+ if (!isValidPercentValue(value)) {
9630
+ throw new Error("ViewportAnchorX must be between 0 and 100.");
9631
+ }
9632
+ _viewportAnchorX = value;
9633
+ }
9634
+ },
9635
+ "scroll": {
9636
+ enumerable: true,
9637
+ get: function get() {
9638
+ return _scroll;
9639
+ },
9640
+ set: function set(value) {
9641
+ var setting = findScrollSetting(value);
9642
+ // Have to check for false as an empty string is a legal value.
9643
+ if (setting === false) {
9644
+ throw new SyntaxError("An invalid or illegal string was specified.");
9645
+ }
9646
+ _scroll = setting;
9647
+ }
9648
+ }
9649
+ });
9650
+ }
9651
+
9652
+ var vttregion = VTTRegion;
9653
+
9654
+ var vttregion$1 = /*#__PURE__*/Object.freeze({
9655
+ default: vttregion,
9656
+ __moduleExports: vttregion
9657
+ });
9658
+
9659
+ var require$$0 = ( vtt$1 && vtt ) || vtt$1;
9660
+
9661
+ var require$$1 = ( vttcue$1 && vttcue ) || vttcue$1;
9662
+
9663
+ var require$$2 = ( vttregion$1 && vttregion ) || vttregion$1;
9664
+
9665
+ var browserIndex = createCommonjsModule(function (module) {
9666
+ /**
9667
+ * Copyright 2013 vtt.js Contributors
9668
+ *
9669
+ * Licensed under the Apache License, Version 2.0 (the "License");
9670
+ * you may not use this file except in compliance with the License.
9671
+ * You may obtain a copy of the License at
9672
+ *
9673
+ * http://www.apache.org/licenses/LICENSE-2.0
9674
+ *
9675
+ * Unless required by applicable law or agreed to in writing, software
9676
+ * distributed under the License is distributed on an "AS IS" BASIS,
9677
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9678
+ * See the License for the specific language governing permissions and
9679
+ * limitations under the License.
9680
+ */
9681
+
9682
+ // Default exports for Node. Export the extended versions of VTTCue and
9683
+ // VTTRegion in Node since we likely want the capability to convert back and
9684
+ // forth between JSON. If we don't then it's not that big of a deal since we're
9685
+ // off browser.
9686
+
9687
+
9688
+ var vttjs = module.exports = {
9689
+ WebVTT: require$$0,
9690
+ VTTCue: require$$1,
9691
+ VTTRegion: require$$2
9692
+ };
9693
+
9694
+ window_1.vttjs = vttjs;
9695
+ window_1.WebVTT = vttjs.WebVTT;
9696
+
9697
+ var cueShim = vttjs.VTTCue;
9698
+ var regionShim = vttjs.VTTRegion;
9699
+ var nativeVTTCue = window_1.VTTCue;
9700
+ var nativeVTTRegion = window_1.VTTRegion;
9701
+
9702
+ vttjs.shim = function () {
9703
+ window_1.VTTCue = cueShim;
9704
+ window_1.VTTRegion = regionShim;
9705
+ };
9706
+
9707
+ vttjs.restore = function () {
9708
+ window_1.VTTCue = nativeVTTCue;
9709
+ window_1.VTTRegion = nativeVTTRegion;
9710
+ };
9711
+
9712
+ if (!window_1.VTTCue) {
9713
+ vttjs.shim();
9714
+ }
9715
+ });
9716
+ var browserIndex_1 = browserIndex.WebVTT;
9717
+ var browserIndex_2 = browserIndex.VTTCue;
9718
+ var browserIndex_3 = browserIndex.VTTRegion;
9719
+
9720
+ /**
9721
+ * @file tech.js
9722
+ */
9723
+
9724
+ /**
9725
+ * An Object containing a structure like: `{src: 'url', type: 'mimetype'}` or string
9726
+ * that just contains the src url alone.
9727
+ * * `var SourceObject = {src: 'http://ex.com/video.mp4', type: 'video/mp4'};`
9728
+ * `var SourceString = 'http://example.com/some-video.mp4';`
9729
+ *
9730
+ * @typedef {Object|string} Tech~SourceObject
9731
+ *
9732
+ * @property {string} src
9733
+ * The url to the source
9734
+ *
9735
+ * @property {string} type
9736
+ * The mime type of the source
9737
+ */
9738
+
9739
+ /**
9740
+ * A function used by {@link Tech} to create a new {@link TextTrack}.
9741
+ *
9742
+ * @private
9743
+ *
9744
+ * @param {Tech} self
9745
+ * An instance of the Tech class.
9746
+ *
9747
+ * @param {string} kind
9748
+ * `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata)
9749
+ *
9750
+ * @param {string} [label]
9751
+ * Label to identify the text track
9752
+ *
9753
+ * @param {string} [language]
9754
+ * Two letter language abbreviation
9755
+ *
9756
+ * @param {Object} [options={}]
9757
+ * An object with additional text track options
9758
+ *
9759
+ * @return {TextTrack}
9760
+ * The text track that was created.
9761
+ */
9762
+ function createTrackHelper(self, kind, label, language) {
9763
+ var options = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : {};
9764
+
9765
+ var tracks = self.textTracks();
9766
+
9767
+ options.kind = kind;
9768
+
9769
+ if (label) {
9770
+ options.label = label;
9771
+ }
9772
+ if (language) {
9773
+ options.language = language;
9774
+ }
9775
+ options.tech = self;
9776
+
9777
+ var track = new ALL.text.TrackClass(options);
9778
+
9779
+ tracks.addTrack(track);
9780
+
9781
+ return track;
9782
+ }
9783
+
9784
+ /**
9785
+ * This is the base class for media playback technology controllers, such as
9786
+ * {@link Flash} and {@link HTML5}
9787
+ *
9788
+ * @extends Component
9789
+ */
9790
+
9791
+ var Tech = function (_Component) {
9792
+ inherits(Tech, _Component);
9793
+
9794
+ /**
9795
+ * Create an instance of this Tech.
9796
+ *
9797
+ * @param {Object} [options]
9798
+ * The key/value store of player options.
9799
+ *
9800
+ * @param {Component~ReadyCallback} ready
9801
+ * Callback function to call when the `HTML5` Tech is ready.
9802
+ */
9803
+ function Tech() {
9804
+ var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
9805
+ var ready = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : function () {};
9806
+ classCallCheck(this, Tech);
9807
+
9808
+ // we don't want the tech to report user activity automatically.
9809
+ // This is done manually in addControlsListeners
9810
+ options.reportTouchActivity = false;
9811
+
9812
+ // keep track of whether the current source has played at all to
9813
+ // implement a very limited played()
9814
+ var _this = possibleConstructorReturn(this, _Component.call(this, null, options, ready));
9815
+
9816
+ _this.hasStarted_ = false;
9817
+ _this.on('playing', function () {
9818
+ this.hasStarted_ = true;
9819
+ });
9820
+ _this.on('loadstart', function () {
9821
+ this.hasStarted_ = false;
9822
+ });
9823
+
9824
+ ALL.names.forEach(function (name) {
9825
+ var props = ALL[name];
9826
+
9827
+ if (options && options[props.getterName]) {
9828
+ _this[props.privateName] = options[props.getterName];
9829
+ }
9830
+ });
9831
+
9832
+ // Manually track progress in cases where the browser/flash player doesn't report it.
9833
+ if (!_this.featuresProgressEvents) {
9834
+ _this.manualProgressOn();
9835
+ }
9836
+
9837
+ // Manually track timeupdates in cases where the browser/flash player doesn't report it.
9838
+ if (!_this.featuresTimeupdateEvents) {
9839
+ _this.manualTimeUpdatesOn();
9840
+ }
9841
+
9842
+ ['Text', 'Audio', 'Video'].forEach(function (track) {
9843
+ if (options['native' + track + 'Tracks'] === false) {
9844
+ _this['featuresNative' + track + 'Tracks'] = false;
9845
+ }
9846
+ });
9847
+
9848
+ if (options.nativeCaptions === false || options.nativeTextTracks === false) {
9849
+ _this.featuresNativeTextTracks = false;
9850
+ } else if (options.nativeCaptions === true || options.nativeTextTracks === true) {
9851
+ _this.featuresNativeTextTracks = true;
9852
+ }
9853
+
9854
+ if (!_this.featuresNativeTextTracks) {
9855
+ _this.emulateTextTracks();
9856
+ }
9857
+
9858
+ _this.autoRemoteTextTracks_ = new ALL.text.ListClass();
9859
+
9860
+ _this.initTrackListeners();
9861
+
9862
+ // Turn on component tap events only if not using native controls
9863
+ if (!options.nativeControlsForTouch) {
9864
+ _this.emitTapEvents();
9865
+ }
9866
+
9867
+ if (_this.constructor) {
9868
+ _this.name_ = _this.constructor.name || 'Unknown Tech';
9869
+ }
9870
+ return _this;
9871
+ }
9872
+
9873
+ /**
9874
+ * A special function to trigger source set in a way that will allow player
9875
+ * to re-trigger if the player or tech are not ready yet.
9876
+ *
9877
+ * @fires Tech#sourceset
9878
+ * @param {string} src The source string at the time of the source changing.
9879
+ */
9880
+
9881
+
9882
+ Tech.prototype.triggerSourceset = function triggerSourceset(src) {
9883
+ var _this2 = this;
9884
+
9885
+ if (!this.isReady_) {
9886
+ // on initial ready we have to trigger source set
9887
+ // 1ms after ready so that player can watch for it.
9888
+ this.one('ready', function () {
9889
+ return _this2.setTimeout(function () {
9890
+ return _this2.triggerSourceset(src);
9891
+ }, 1);
9892
+ });
9893
+ }
9894
+
9895
+ /**
9896
+ * Fired when the source is set on the tech causing the media element
9897
+ * to reload.
9898
+ *
9899
+ * @see {@link Player#event:sourceset}
9900
+ * @event Tech#sourceset
9901
+ * @type {EventTarget~Event}
9902
+ */
9903
+ this.trigger({
9904
+ src: src,
9905
+ type: 'sourceset'
9906
+ });
9907
+ };
9908
+
9909
+ /* Fallbacks for unsupported event types
9910
+ ================================================================================ */
9911
+
9912
+ /**
9913
+ * Polyfill the `progress` event for browsers that don't support it natively.
9914
+ *
9915
+ * @see {@link Tech#trackProgress}
9916
+ */
9917
+
9918
+
9919
+ Tech.prototype.manualProgressOn = function manualProgressOn() {
9920
+ this.on('durationchange', this.onDurationChange);
9921
+
9922
+ this.manualProgress = true;
9923
+
9924
+ // Trigger progress watching when a source begins loading
9925
+ this.one('ready', this.trackProgress);
9926
+ };
9927
+
9928
+ /**
9929
+ * Turn off the polyfill for `progress` events that was created in
9930
+ * {@link Tech#manualProgressOn}
9931
+ */
9932
+
9933
+
9934
+ Tech.prototype.manualProgressOff = function manualProgressOff() {
9935
+ this.manualProgress = false;
9936
+ this.stopTrackingProgress();
9937
+
9938
+ this.off('durationchange', this.onDurationChange);
9939
+ };
9940
+
9941
+ /**
9942
+ * This is used to trigger a `progress` event when the buffered percent changes. It
9943
+ * sets an interval function that will be called every 500 milliseconds to check if the
9944
+ * buffer end percent has changed.
9945
+ *
9946
+ * > This function is called by {@link Tech#manualProgressOn}
9947
+ *
9948
+ * @param {EventTarget~Event} event
9949
+ * The `ready` event that caused this to run.
9950
+ *
9951
+ * @listens Tech#ready
9952
+ * @fires Tech#progress
9953
+ */
9954
+
9955
+
9956
+ Tech.prototype.trackProgress = function trackProgress(event) {
9957
+ this.stopTrackingProgress();
9958
+ this.progressInterval = this.setInterval(bind(this, function () {
9959
+ // Don't trigger unless buffered amount is greater than last time
9960
+
9961
+ var numBufferedPercent = this.bufferedPercent();
9962
+
9963
+ if (this.bufferedPercent_ !== numBufferedPercent) {
9964
+ /**
9965
+ * See {@link Player#progress}
9966
+ *
9967
+ * @event Tech#progress
9968
+ * @type {EventTarget~Event}
9969
+ */
9970
+ this.trigger('progress');
9971
+ }
9972
+
9973
+ this.bufferedPercent_ = numBufferedPercent;
9974
+
9975
+ if (numBufferedPercent === 1) {
9976
+ this.stopTrackingProgress();
9977
+ }
9978
+ }), 500);
9979
+ };
9980
+
9981
+ /**
9982
+ * Update our internal duration on a `durationchange` event by calling
9983
+ * {@link Tech#duration}.
9984
+ *
9985
+ * @param {EventTarget~Event} event
9986
+ * The `durationchange` event that caused this to run.
9987
+ *
9988
+ * @listens Tech#durationchange
9989
+ */
9990
+
9991
+
9992
+ Tech.prototype.onDurationChange = function onDurationChange(event) {
9993
+ this.duration_ = this.duration();
9994
+ };
9995
+
9996
+ /**
9997
+ * Get and create a `TimeRange` object for buffering.
9998
+ *
9999
+ * @return {TimeRange}
10000
+ * The time range object that was created.
10001
+ */
10002
+
10003
+
10004
+ Tech.prototype.buffered = function buffered() {
10005
+ return createTimeRanges(0, 0);
10006
+ };
10007
+
10008
+ /**
10009
+ * Get the percentage of the current video that is currently buffered.
10010
+ *
10011
+ * @return {number}
10012
+ * A number from 0 to 1 that represents the decimal percentage of the
10013
+ * video that is buffered.
10014
+ *
10015
+ */
10016
+
10017
+
10018
+ Tech.prototype.bufferedPercent = function bufferedPercent$$1() {
10019
+ return bufferedPercent(this.buffered(), this.duration_);
10020
+ };
10021
+
10022
+ /**
10023
+ * Turn off the polyfill for `progress` events that was created in
10024
+ * {@link Tech#manualProgressOn}
10025
+ * Stop manually tracking progress events by clearing the interval that was set in
10026
+ * {@link Tech#trackProgress}.
10027
+ */
10028
+
10029
+
10030
+ Tech.prototype.stopTrackingProgress = function stopTrackingProgress() {
10031
+ this.clearInterval(this.progressInterval);
10032
+ };
10033
+
10034
+ /**
10035
+ * Polyfill the `timeupdate` event for browsers that don't support it.
10036
+ *
10037
+ * @see {@link Tech#trackCurrentTime}
10038
+ */
10039
+
10040
+
10041
+ Tech.prototype.manualTimeUpdatesOn = function manualTimeUpdatesOn() {
10042
+ this.manualTimeUpdates = true;
10043
+
10044
+ this.on('play', this.trackCurrentTime);
10045
+ this.on('pause', this.stopTrackingCurrentTime);
10046
+ };
10047
+
10048
+ /**
10049
+ * Turn off the polyfill for `timeupdate` events that was created in
10050
+ * {@link Tech#manualTimeUpdatesOn}
10051
+ */
10052
+
10053
+
10054
+ Tech.prototype.manualTimeUpdatesOff = function manualTimeUpdatesOff() {
10055
+ this.manualTimeUpdates = false;
10056
+ this.stopTrackingCurrentTime();
10057
+ this.off('play', this.trackCurrentTime);
10058
+ this.off('pause', this.stopTrackingCurrentTime);
10059
+ };
10060
+
10061
+ /**
10062
+ * Sets up an interval function to track current time and trigger `timeupdate` every
10063
+ * 250 milliseconds.
10064
+ *
10065
+ * @listens Tech#play
10066
+ * @triggers Tech#timeupdate
10067
+ */
10068
+
10069
+
10070
+ Tech.prototype.trackCurrentTime = function trackCurrentTime() {
10071
+ if (this.currentTimeInterval) {
10072
+ this.stopTrackingCurrentTime();
10073
+ }
10074
+ this.currentTimeInterval = this.setInterval(function () {
10075
+ /**
10076
+ * Triggered at an interval of 250ms to indicated that time is passing in the video.
10077
+ *
10078
+ * @event Tech#timeupdate
10079
+ * @type {EventTarget~Event}
10080
+ */
10081
+ this.trigger({ type: 'timeupdate', target: this, manuallyTriggered: true });
10082
+
10083
+ // 42 = 24 fps // 250 is what Webkit uses // FF uses 15
10084
+ }, 250);
10085
+ };
10086
+
10087
+ /**
10088
+ * Stop the interval function created in {@link Tech#trackCurrentTime} so that the
10089
+ * `timeupdate` event is no longer triggered.
10090
+ *
10091
+ * @listens {Tech#pause}
10092
+ */
10093
+
10094
+
10095
+ Tech.prototype.stopTrackingCurrentTime = function stopTrackingCurrentTime() {
10096
+ this.clearInterval(this.currentTimeInterval);
10097
+
10098
+ // #1002 - if the video ends right before the next timeupdate would happen,
10099
+ // the progress bar won't make it all the way to the end
10100
+ this.trigger({ type: 'timeupdate', target: this, manuallyTriggered: true });
10101
+ };
10102
+
10103
+ /**
10104
+ * Turn off all event polyfills, clear the `Tech`s {@link AudioTrackList},
10105
+ * {@link VideoTrackList}, and {@link TextTrackList}, and dispose of this Tech.
10106
+ *
10107
+ * @fires Component#dispose
10108
+ */
10109
+
10110
+
10111
+ Tech.prototype.dispose = function dispose() {
10112
+
10113
+ // clear out all tracks because we can't reuse them between techs
10114
+ this.clearTracks(NORMAL.names);
10115
+
10116
+ // Turn off any manual progress or timeupdate tracking
10117
+ if (this.manualProgress) {
10118
+ this.manualProgressOff();
10119
+ }
10120
+
10121
+ if (this.manualTimeUpdates) {
10122
+ this.manualTimeUpdatesOff();
10123
+ }
10124
+
10125
+ _Component.prototype.dispose.call(this);
10126
+ };
10127
+
10128
+ /**
10129
+ * Clear out a single `TrackList` or an array of `TrackLists` given their names.
10130
+ *
10131
+ * > Note: Techs without source handlers should call this between sources for `video`
10132
+ * & `audio` tracks. You don't want to use them between tracks!
10133
+ *
10134
+ * @param {string[]|string} types
10135
+ * TrackList names to clear, valid names are `video`, `audio`, and
10136
+ * `text`.
10137
+ */
10138
+
10139
+
10140
+ Tech.prototype.clearTracks = function clearTracks(types) {
10141
+ var _this3 = this;
10142
+
10143
+ types = [].concat(types);
10144
+ // clear out all tracks because we can't reuse them between techs
10145
+ types.forEach(function (type) {
10146
+ var list = _this3[type + 'Tracks']() || [];
10147
+ var i = list.length;
10148
+
10149
+ while (i--) {
10150
+ var track = list[i];
10151
+
10152
+ if (type === 'text') {
10153
+ _this3.removeRemoteTextTrack(track);
10154
+ }
10155
+ list.removeTrack(track);
10156
+ }
10157
+ });
10158
+ };
10159
+
10160
+ /**
10161
+ * Remove any TextTracks added via addRemoteTextTrack that are
10162
+ * flagged for automatic garbage collection
10163
+ */
10164
+
10165
+
10166
+ Tech.prototype.cleanupAutoTextTracks = function cleanupAutoTextTracks() {
10167
+ var list = this.autoRemoteTextTracks_ || [];
10168
+ var i = list.length;
10169
+
10170
+ while (i--) {
10171
+ var track = list[i];
10172
+
10173
+ this.removeRemoteTextTrack(track);
10174
+ }
10175
+ };
10176
+
10177
+ /**
10178
+ * Reset the tech, which will removes all sources and reset the internal readyState.
10179
+ *
10180
+ * @abstract
10181
+ */
10182
+
10183
+
10184
+ Tech.prototype.reset = function reset() {};
10185
+
10186
+ /**
10187
+ * Get or set an error on the Tech.
10188
+ *
10189
+ * @param {MediaError} [err]
10190
+ * Error to set on the Tech
10191
+ *
10192
+ * @return {MediaError|null}
10193
+ * The current error object on the tech, or null if there isn't one.
10194
+ */
10195
+
10196
+
10197
+ Tech.prototype.error = function error(err) {
10198
+ if (err !== undefined) {
10199
+ this.error_ = new MediaError(err);
10200
+ this.trigger('error');
10201
+ }
10202
+ return this.error_;
10203
+ };
10204
+
10205
+ /**
10206
+ * Returns the `TimeRange`s that have been played through for the current source.
10207
+ *
10208
+ * > NOTE: This implementation is incomplete. It does not track the played `TimeRange`.
10209
+ * It only checks whether the source has played at all or not.
10210
+ *
10211
+ * @return {TimeRange}
10212
+ * - A single time range if this video has played
10213
+ * - An empty set of ranges if not.
10214
+ */
10215
+
10216
+
10217
+ Tech.prototype.played = function played() {
10218
+ if (this.hasStarted_) {
10219
+ return createTimeRanges(0, 0);
10220
+ }
10221
+ return createTimeRanges();
10222
+ };
10223
+
10224
+ /**
10225
+ * Causes a manual time update to occur if {@link Tech#manualTimeUpdatesOn} was
10226
+ * previously called.
10227
+ *
10228
+ * @fires Tech#timeupdate
10229
+ */
10230
+
10231
+
10232
+ Tech.prototype.setCurrentTime = function setCurrentTime() {
10233
+ // improve the accuracy of manual timeupdates
10234
+ if (this.manualTimeUpdates) {
10235
+ /**
10236
+ * A manual `timeupdate` event.
10237
+ *
10238
+ * @event Tech#timeupdate
10239
+ * @type {EventTarget~Event}
10240
+ */
10241
+ this.trigger({ type: 'timeupdate', target: this, manuallyTriggered: true });
10242
+ }
10243
+ };
10244
+
10245
+ /**
10246
+ * Turn on listeners for {@link VideoTrackList}, {@link {AudioTrackList}, and
10247
+ * {@link TextTrackList} events.
10248
+ *
10249
+ * This adds {@link EventTarget~EventListeners} for `addtrack`, and `removetrack`.
10250
+ *
10251
+ * @fires Tech#audiotrackchange
10252
+ * @fires Tech#videotrackchange
10253
+ * @fires Tech#texttrackchange
10254
+ */
10255
+
10256
+
10257
+ Tech.prototype.initTrackListeners = function initTrackListeners() {
10258
+ var _this4 = this;
10259
+
10260
+ /**
10261
+ * Triggered when tracks are added or removed on the Tech {@link AudioTrackList}
10262
+ *
10263
+ * @event Tech#audiotrackchange
10264
+ * @type {EventTarget~Event}
10265
+ */
10266
+
10267
+ /**
10268
+ * Triggered when tracks are added or removed on the Tech {@link VideoTrackList}
10269
+ *
10270
+ * @event Tech#videotrackchange
10271
+ * @type {EventTarget~Event}
10272
+ */
10273
+
10274
+ /**
10275
+ * Triggered when tracks are added or removed on the Tech {@link TextTrackList}
10276
+ *
10277
+ * @event Tech#texttrackchange
10278
+ * @type {EventTarget~Event}
10279
+ */
10280
+ NORMAL.names.forEach(function (name) {
10281
+ var props = NORMAL[name];
10282
+ var trackListChanges = function trackListChanges() {
10283
+ _this4.trigger(name + 'trackchange');
10284
+ };
10285
+
10286
+ var tracks = _this4[props.getterName]();
10287
+
10288
+ tracks.addEventListener('removetrack', trackListChanges);
10289
+ tracks.addEventListener('addtrack', trackListChanges);
10290
+
10291
+ _this4.on('dispose', function () {
10292
+ tracks.removeEventListener('removetrack', trackListChanges);
10293
+ tracks.removeEventListener('addtrack', trackListChanges);
10294
+ });
10295
+ });
10296
+ };
10297
+
10298
+ /**
10299
+ * Emulate TextTracks using vtt.js if necessary
10300
+ *
10301
+ * @fires Tech#vttjsloaded
10302
+ * @fires Tech#vttjserror
10303
+ */
10304
+
10305
+
10306
+ Tech.prototype.addWebVttScript_ = function addWebVttScript_() {
10307
+ var _this5 = this;
10308
+
10309
+ if (window_1.WebVTT) {
10310
+ return;
10311
+ }
10312
+
10313
+ // Initially, Tech.el_ is a child of a dummy-div wait until the Component system
10314
+ // signals that the Tech is ready at which point Tech.el_ is part of the DOM
10315
+ // before inserting the WebVTT script
10316
+ if (document_1.body.contains(this.el())) {
10317
+
10318
+ // load via require if available and vtt.js script location was not passed in
10319
+ // as an option. novtt builds will turn the above require call into an empty object
10320
+ // which will cause this if check to always fail.
10321
+ if (!this.options_['vtt.js'] && isPlain(browserIndex) && Object.keys(browserIndex).length > 0) {
10322
+ this.trigger('vttjsloaded');
10323
+ return;
10324
+ }
10325
+
10326
+ // load vtt.js via the script location option or the cdn of no location was
10327
+ // passed in
10328
+ var script = document_1.createElement('script');
10329
+
10330
+ script.src = this.options_['vtt.js'] || 'https://vjs.zencdn.net/vttjs/0.14.1/vtt.min.js';
10331
+ script.onload = function () {
10332
+ /**
10333
+ * Fired when vtt.js is loaded.
10334
+ *
10335
+ * @event Tech#vttjsloaded
10336
+ * @type {EventTarget~Event}
10337
+ */
10338
+ _this5.trigger('vttjsloaded');
10339
+ };
10340
+ script.onerror = function () {
10341
+ /**
10342
+ * Fired when vtt.js was not loaded due to an error
10343
+ *
10344
+ * @event Tech#vttjsloaded
10345
+ * @type {EventTarget~Event}
10346
+ */
10347
+ _this5.trigger('vttjserror');
10348
+ };
10349
+ this.on('dispose', function () {
10350
+ script.onload = null;
10351
+ script.onerror = null;
10352
+ });
10353
+ // but have not loaded yet and we set it to true before the inject so that
10354
+ // we don't overwrite the injected window.WebVTT if it loads right away
10355
+ window_1.WebVTT = true;
10356
+ this.el().parentNode.appendChild(script);
10357
+ } else {
10358
+ this.ready(this.addWebVttScript_);
10359
+ }
10360
+ };
10361
+
10362
+ /**
10363
+ * Emulate texttracks
10364
+ *
10365
+ */
10366
+
10367
+
10368
+ Tech.prototype.emulateTextTracks = function emulateTextTracks() {
10369
+ var _this6 = this;
10370
+
10371
+ var tracks = this.textTracks();
10372
+ var remoteTracks = this.remoteTextTracks();
10373
+ var handleAddTrack = function handleAddTrack(e) {
10374
+ return tracks.addTrack(e.track);
10375
+ };
10376
+ var handleRemoveTrack = function handleRemoveTrack(e) {
10377
+ return tracks.removeTrack(e.track);
10378
+ };
10379
+
10380
+ remoteTracks.on('addtrack', handleAddTrack);
10381
+ remoteTracks.on('removetrack', handleRemoveTrack);
10382
+
10383
+ this.addWebVttScript_();
10384
+
10385
+ var updateDisplay = function updateDisplay() {
10386
+ return _this6.trigger('texttrackchange');
10387
+ };
10388
+
10389
+ var textTracksChanges = function textTracksChanges() {
10390
+ updateDisplay();
10391
+
10392
+ for (var i = 0; i < tracks.length; i++) {
10393
+ var track = tracks[i];
10394
+
10395
+ track.removeEventListener('cuechange', updateDisplay);
10396
+ if (track.mode === 'showing') {
10397
+ track.addEventListener('cuechange', updateDisplay);
10398
+ }
10399
+ }
10400
+ };
10401
+
10402
+ textTracksChanges();
10403
+ tracks.addEventListener('change', textTracksChanges);
10404
+ tracks.addEventListener('addtrack', textTracksChanges);
10405
+ tracks.addEventListener('removetrack', textTracksChanges);
10406
+
10407
+ this.on('dispose', function () {
10408
+ remoteTracks.off('addtrack', handleAddTrack);
10409
+ remoteTracks.off('removetrack', handleRemoveTrack);
10410
+ tracks.removeEventListener('change', textTracksChanges);
10411
+ tracks.removeEventListener('addtrack', textTracksChanges);
10412
+ tracks.removeEventListener('removetrack', textTracksChanges);
10413
+
10414
+ for (var i = 0; i < tracks.length; i++) {
10415
+ var track = tracks[i];
10416
+
10417
+ track.removeEventListener('cuechange', updateDisplay);
10418
+ }
10419
+ });
10420
+ };
10421
+
10422
+ /**
10423
+ * Create and returns a remote {@link TextTrack} object.
10424
+ *
10425
+ * @param {string} kind
10426
+ * `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata)
10427
+ *
10428
+ * @param {string} [label]
10429
+ * Label to identify the text track
10430
+ *
10431
+ * @param {string} [language]
10432
+ * Two letter language abbreviation
10433
+ *
10434
+ * @return {TextTrack}
10435
+ * The TextTrack that gets created.
10436
+ */
10437
+
10438
+
10439
+ Tech.prototype.addTextTrack = function addTextTrack(kind, label, language) {
10440
+ if (!kind) {
10441
+ throw new Error('TextTrack kind is required but was not provided');
10442
+ }
10443
+
10444
+ return createTrackHelper(this, kind, label, language);
10445
+ };
10446
+
10447
+ /**
10448
+ * Create an emulated TextTrack for use by addRemoteTextTrack
10449
+ *
10450
+ * This is intended to be overridden by classes that inherit from
10451
+ * Tech in order to create native or custom TextTracks.
10452
+ *
10453
+ * @param {Object} options
10454
+ * The object should contain the options to initialize the TextTrack with.
10455
+ *
10456
+ * @param {string} [options.kind]
10457
+ * `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata).
10458
+ *
10459
+ * @param {string} [options.label].
10460
+ * Label to identify the text track
10461
+ *
10462
+ * @param {string} [options.language]
10463
+ * Two letter language abbreviation.
10464
+ *
10465
+ * @return {HTMLTrackElement}
10466
+ * The track element that gets created.
10467
+ */
10468
+
10469
+
10470
+ Tech.prototype.createRemoteTextTrack = function createRemoteTextTrack(options) {
10471
+ var track = mergeOptions(options, {
10472
+ tech: this
10473
+ });
10474
+
10475
+ return new REMOTE.remoteTextEl.TrackClass(track);
10476
+ };
10477
+
10478
+ /**
10479
+ * Creates a remote text track object and returns an html track element.
10480
+ *
10481
+ * > Note: This can be an emulated {@link HTMLTrackElement} or a native one.
10482
+ *
10483
+ * @param {Object} options
10484
+ * See {@link Tech#createRemoteTextTrack} for more detailed properties.
10485
+ *
10486
+ * @param {boolean} [manualCleanup=true]
10487
+ * - When false: the TextTrack will be automatically removed from the video
10488
+ * element whenever the source changes
10489
+ * - When True: The TextTrack will have to be cleaned up manually
10490
+ *
10491
+ * @return {HTMLTrackElement}
10492
+ * An Html Track Element.
10493
+ *
10494
+ * @deprecated The default functionality for this function will be equivalent
10495
+ * to "manualCleanup=false" in the future. The manualCleanup parameter will
10496
+ * also be removed.
10497
+ */
10498
+
10499
+
10500
+ Tech.prototype.addRemoteTextTrack = function addRemoteTextTrack() {
10501
+ var _this7 = this;
10502
+
10503
+ var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
10504
+ var manualCleanup = arguments[1];
10505
+
10506
+ var htmlTrackElement = this.createRemoteTextTrack(options);
10507
+
10508
+ if (manualCleanup !== true && manualCleanup !== false) {
10509
+ // deprecation warning
10510
+ log$1.warn('Calling addRemoteTextTrack without explicitly setting the "manualCleanup" parameter to `true` is deprecated and default to `false` in future version of video.js');
10511
+ manualCleanup = true;
10512
+ }
10513
+
10514
+ // store HTMLTrackElement and TextTrack to remote list
10515
+ this.remoteTextTrackEls().addTrackElement_(htmlTrackElement);
10516
+ this.remoteTextTracks().addTrack(htmlTrackElement.track);
10517
+
10518
+ if (manualCleanup !== true) {
10519
+ // create the TextTrackList if it doesn't exist
10520
+ this.ready(function () {
10521
+ return _this7.autoRemoteTextTracks_.addTrack(htmlTrackElement.track);
10522
+ });
10523
+ }
10524
+
10525
+ return htmlTrackElement;
10526
+ };
10527
+
10528
+ /**
10529
+ * Remove a remote text track from the remote `TextTrackList`.
10530
+ *
10531
+ * @param {TextTrack} track
10532
+ * `TextTrack` to remove from the `TextTrackList`
10533
+ */
10534
+
10535
+
10536
+ Tech.prototype.removeRemoteTextTrack = function removeRemoteTextTrack(track) {
10537
+ var trackElement = this.remoteTextTrackEls().getTrackElementByTrack_(track);
10538
+
10539
+ // remove HTMLTrackElement and TextTrack from remote list
10540
+ this.remoteTextTrackEls().removeTrackElement_(trackElement);
10541
+ this.remoteTextTracks().removeTrack(track);
10542
+ this.autoRemoteTextTracks_.removeTrack(track);
10543
+ };
10544
+
10545
+ /**
10546
+ * Gets available media playback quality metrics as specified by the W3C's Media
10547
+ * Playback Quality API.
10548
+ *
10549
+ * @see [Spec]{@link https://wicg.github.io/media-playback-quality}
10550
+ *
10551
+ * @return {Object}
10552
+ * An object with supported media playback quality metrics
10553
+ *
10554
+ * @abstract
10555
+ */
10556
+
10557
+
10558
+ Tech.prototype.getVideoPlaybackQuality = function getVideoPlaybackQuality() {
10559
+ return {};
10560
+ };
10561
+
10562
+ /**
10563
+ * A method to set a poster from a `Tech`.
10564
+ *
10565
+ * @abstract
10566
+ */
10567
+
10568
+
10569
+ Tech.prototype.setPoster = function setPoster() {};
10570
+
10571
+ /**
10572
+ * A method to check for the presence of the 'playsinline' <video> attribute.
10573
+ *
10574
+ * @abstract
10575
+ */
10576
+
10577
+
10578
+ Tech.prototype.playsinline = function playsinline() {};
10579
+
10580
+ /**
10581
+ * A method to set or unset the 'playsinline' <video> attribute.
10582
+ *
10583
+ * @abstract
10584
+ */
10585
+
10586
+
10587
+ Tech.prototype.setPlaysinline = function setPlaysinline() {};
10588
+
10589
+ /**
10590
+ * Attempt to force override of native audio tracks.
10591
+ *
10592
+ * @param {Boolean} override - If set to true native audio will be overridden,
10593
+ * otherwise native audio will potentially be used.
10594
+ *
10595
+ * @abstract
10596
+ */
10597
+
10598
+
10599
+ Tech.prototype.overrideNativeAudioTracks = function overrideNativeAudioTracks() {};
10600
+
10601
+ /**
10602
+ * Attempt to force override of native video tracks.
10603
+ *
10604
+ * @param {Boolean} override - If set to true native video will be overridden,
10605
+ * otherwise native video will potentially be used.
10606
+ *
10607
+ * @abstract
10608
+ */
10609
+
10610
+
10611
+ Tech.prototype.overrideNativeVideoTracks = function overrideNativeVideoTracks() {};
10612
+
10613
+ /*
10614
+ * Check if the tech can support the given mime-type.
10615
+ *
10616
+ * The base tech does not support any type, but source handlers might
10617
+ * overwrite this.
10618
+ *
10619
+ * @param {string} type
10620
+ * The mimetype to check for support
10621
+ *
10622
+ * @return {string}
10623
+ * 'probably', 'maybe', or empty string
10624
+ *
10625
+ * @see [Spec]{@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/canPlayType}
10626
+ *
10627
+ * @abstract
10628
+ */
10629
+
10630
+
10631
+ Tech.prototype.canPlayType = function canPlayType() {
10632
+ return '';
10633
+ };
10634
+
10635
+ /**
10636
+ * Check if the type is supported by this tech.
10637
+ *
10638
+ * The base tech does not support any type, but source handlers might
10639
+ * overwrite this.
10640
+ *
10641
+ * @param {string} type
10642
+ * The media type to check
10643
+ * @return {string} Returns the native video element's response
10644
+ */
10645
+
10646
+
10647
+ Tech.canPlayType = function canPlayType() {
10648
+ return '';
10649
+ };
10650
+
10651
+ /**
10652
+ * Check if the tech can support the given source
10653
+ * @param {Object} srcObj
10654
+ * The source object
10655
+ * @param {Object} options
10656
+ * The options passed to the tech
10657
+ * @return {string} 'probably', 'maybe', or '' (empty string)
10658
+ */
10659
+
10660
+
10661
+ Tech.canPlaySource = function canPlaySource(srcObj, options) {
10662
+ return Tech.canPlayType(srcObj.type);
10663
+ };
10664
+
10665
+ /*
10666
+ * Return whether the argument is a Tech or not.
10667
+ * Can be passed either a Class like `Html5` or a instance like `player.tech_`
10668
+ *
10669
+ * @param {Object} component
10670
+ * The item to check
10671
+ *
10672
+ * @return {boolean}
10673
+ * Whether it is a tech or not
10674
+ * - True if it is a tech
10675
+ * - False if it is not
10676
+ */
10677
+
10678
+
10679
+ Tech.isTech = function isTech(component) {
10680
+ return component.prototype instanceof Tech || component instanceof Tech || component === Tech;
10681
+ };
10682
+
10683
+ /**
10684
+ * Registers a `Tech` into a shared list for videojs.
10685
+ *
10686
+ * @param {string} name
10687
+ * Name of the `Tech` to register.
10688
+ *
10689
+ * @param {Object} tech
10690
+ * The `Tech` class to register.
10691
+ */
10692
+
10693
+
10694
+ Tech.registerTech = function registerTech(name, tech) {
10695
+ if (!Tech.techs_) {
10696
+ Tech.techs_ = {};
10697
+ }
10698
+
10699
+ if (!Tech.isTech(tech)) {
10700
+ throw new Error('Tech ' + name + ' must be a Tech');
10701
+ }
10702
+
10703
+ if (!Tech.canPlayType) {
10704
+ throw new Error('Techs must have a static canPlayType method on them');
10705
+ }
10706
+ if (!Tech.canPlaySource) {
10707
+ throw new Error('Techs must have a static canPlaySource method on them');
10708
+ }
10709
+
10710
+ name = toTitleCase(name);
10711
+
10712
+ Tech.techs_[name] = tech;
10713
+ if (name !== 'Tech') {
10714
+ // camel case the techName for use in techOrder
10715
+ Tech.defaultTechOrder_.push(name);
10716
+ }
10717
+ return tech;
10718
+ };
10719
+
10720
+ /**
10721
+ * Get a `Tech` from the shared list by name.
10722
+ *
10723
+ * @param {string} name
10724
+ * `camelCase` or `TitleCase` name of the Tech to get
10725
+ *
10726
+ * @return {Tech|undefined}
10727
+ * The `Tech` or undefined if there was no tech with the name requested.
10728
+ */
10729
+
10730
+
10731
+ Tech.getTech = function getTech(name) {
10732
+ if (!name) {
10733
+ return;
10734
+ }
10735
+
10736
+ name = toTitleCase(name);
10737
+
10738
+ if (Tech.techs_ && Tech.techs_[name]) {
10739
+ return Tech.techs_[name];
10740
+ }
10741
+
10742
+ if (window_1 && window_1.videojs && window_1.videojs[name]) {
10743
+ log$1.warn('The ' + name + ' tech was added to the videojs object when it should be registered using videojs.registerTech(name, tech)');
10744
+ return window_1.videojs[name];
10745
+ }
10746
+ };
10747
+
10748
+ return Tech;
10749
+ }(Component);
10750
+
10751
+ /**
10752
+ * Get the {@link VideoTrackList}
10753
+ *
10754
+ * @returns {VideoTrackList}
10755
+ * @method Tech.prototype.videoTracks
10756
+ */
10757
+
10758
+ /**
10759
+ * Get the {@link AudioTrackList}
10760
+ *
10761
+ * @returns {AudioTrackList}
10762
+ * @method Tech.prototype.audioTracks
10763
+ */
10764
+
10765
+ /**
10766
+ * Get the {@link TextTrackList}
10767
+ *
10768
+ * @returns {TextTrackList}
10769
+ * @method Tech.prototype.textTracks
10770
+ */
10771
+
10772
+ /**
10773
+ * Get the remote element {@link TextTrackList}
10774
+ *
10775
+ * @returns {TextTrackList}
10776
+ * @method Tech.prototype.remoteTextTracks
10777
+ */
10778
+
10779
+ /**
10780
+ * Get the remote element {@link HtmlTrackElementList}
10781
+ *
10782
+ * @returns {HtmlTrackElementList}
10783
+ * @method Tech.prototype.remoteTextTrackEls
10784
+ */
10785
+
10786
+ ALL.names.forEach(function (name) {
10787
+ var props = ALL[name];
10788
+
10789
+ Tech.prototype[props.getterName] = function () {
10790
+ this[props.privateName] = this[props.privateName] || new props.ListClass();
10791
+ return this[props.privateName];
10792
+ };
10793
+ });
10794
+
10795
+ /**
10796
+ * List of associated text tracks
10797
+ *
10798
+ * @type {TextTrackList}
10799
+ * @private
10800
+ * @property Tech#textTracks_
10801
+ */
10802
+
10803
+ /**
10804
+ * List of associated audio tracks.
10805
+ *
10806
+ * @type {AudioTrackList}
10807
+ * @private
10808
+ * @property Tech#audioTracks_
10809
+ */
10810
+
10811
+ /**
10812
+ * List of associated video tracks.
10813
+ *
10814
+ * @type {VideoTrackList}
10815
+ * @private
10816
+ * @property Tech#videoTracks_
10817
+ */
10818
+
10819
+ /**
10820
+ * Boolean indicating whether the `Tech` supports volume control.
10821
+ *
10822
+ * @type {boolean}
10823
+ * @default
10824
+ */
10825
+ Tech.prototype.featuresVolumeControl = true;
10826
+
10827
+ /**
10828
+ * Boolean indicating whether the `Tech` supports muting volume.
10829
+ *
10830
+ * @type {bolean}
10831
+ * @default
10832
+ */
10833
+ Tech.prototype.featuresMuteControl = true;
10834
+
10835
+ /**
10836
+ * Boolean indicating whether the `Tech` supports fullscreen resize control.
10837
+ * Resizing plugins using request fullscreen reloads the plugin
10838
+ *
10839
+ * @type {boolean}
10840
+ * @default
10841
+ */
10842
+ Tech.prototype.featuresFullscreenResize = false;
10843
+
10844
+ /**
10845
+ * Boolean indicating whether the `Tech` supports changing the speed at which the video
10846
+ * plays. Examples:
10847
+ * - Set player to play 2x (twice) as fast
10848
+ * - Set player to play 0.5x (half) as fast
10849
+ *
10850
+ * @type {boolean}
10851
+ * @default
10852
+ */
10853
+ Tech.prototype.featuresPlaybackRate = false;
10854
+
10855
+ /**
10856
+ * Boolean indicating whether the `Tech` supports the `progress` event. This is currently
10857
+ * not triggered by video-js-swf. This will be used to determine if
10858
+ * {@link Tech#manualProgressOn} should be called.
10859
+ *
10860
+ * @type {boolean}
10861
+ * @default
10862
+ */
10863
+ Tech.prototype.featuresProgressEvents = false;
10864
+
10865
+ /**
10866
+ * Boolean indicating whether the `Tech` supports the `sourceset` event.
10867
+ *
10868
+ * A tech should set this to `true` and then use {@link Tech#triggerSourceset}
10869
+ * to trigger a {@link Tech#event:sourceset} at the earliest time after getting
10870
+ * a new source.
10871
+ *
10872
+ * @type {boolean}
10873
+ * @default
10874
+ */
10875
+ Tech.prototype.featuresSourceset = false;
10876
+
10877
+ /**
10878
+ * Boolean indicating whether the `Tech` supports the `timeupdate` event. This is currently
10879
+ * not triggered by video-js-swf. This will be used to determine if
10880
+ * {@link Tech#manualTimeUpdates} should be called.
10881
+ *
10882
+ * @type {boolean}
10883
+ * @default
10884
+ */
10885
+ Tech.prototype.featuresTimeupdateEvents = false;
10886
+
10887
+ /**
10888
+ * Boolean indicating whether the `Tech` supports the native `TextTrack`s.
10889
+ * This will help us integrate with native `TextTrack`s if the browser supports them.
10890
+ *
10891
+ * @type {boolean}
10892
+ * @default
10893
+ */
10894
+ Tech.prototype.featuresNativeTextTracks = false;
10895
+
10896
+ /**
10897
+ * A functional mixin for techs that want to use the Source Handler pattern.
10898
+ * Source handlers are scripts for handling specific formats.
10899
+ * The source handler pattern is used for adaptive formats (HLS, DASH) that
10900
+ * manually load video data and feed it into a Source Buffer (Media Source Extensions)
10901
+ * Example: `Tech.withSourceHandlers.call(MyTech);`
10902
+ *
10903
+ * @param {Tech} _Tech
10904
+ * The tech to add source handler functions to.
10905
+ *
10906
+ * @mixes Tech~SourceHandlerAdditions
10907
+ */
10908
+ Tech.withSourceHandlers = function (_Tech) {
10909
+
10910
+ /**
10911
+ * Register a source handler
10912
+ *
10913
+ * @param {Function} handler
10914
+ * The source handler class
10915
+ *
10916
+ * @param {number} [index]
10917
+ * Register it at the following index
10918
+ */
10919
+ _Tech.registerSourceHandler = function (handler, index) {
10920
+ var handlers = _Tech.sourceHandlers;
10921
+
10922
+ if (!handlers) {
10923
+ handlers = _Tech.sourceHandlers = [];
10924
+ }
10925
+
10926
+ if (index === undefined) {
10927
+ // add to the end of the list
10928
+ index = handlers.length;
10929
+ }
10930
+
10931
+ handlers.splice(index, 0, handler);
10932
+ };
10933
+
10934
+ /**
10935
+ * Check if the tech can support the given type. Also checks the
10936
+ * Techs sourceHandlers.
10937
+ *
10938
+ * @param {string} type
10939
+ * The mimetype to check.
10940
+ *
10941
+ * @return {string}
10942
+ * 'probably', 'maybe', or '' (empty string)
10943
+ */
10944
+ _Tech.canPlayType = function (type) {
10945
+ var handlers = _Tech.sourceHandlers || [];
10946
+ var can = void 0;
10947
+
10948
+ for (var i = 0; i < handlers.length; i++) {
10949
+ can = handlers[i].canPlayType(type);
10950
+
10951
+ if (can) {
10952
+ return can;
10953
+ }
10954
+ }
10955
+
10956
+ return '';
10957
+ };
10958
+
10959
+ /**
10960
+ * Returns the first source handler that supports the source.
10961
+ *
10962
+ * TODO: Answer question: should 'probably' be prioritized over 'maybe'
10963
+ *
10964
+ * @param {Tech~SourceObject} source
10965
+ * The source object
10966
+ *
10967
+ * @param {Object} options
10968
+ * The options passed to the tech
10969
+ *
10970
+ * @return {SourceHandler|null}
10971
+ * The first source handler that supports the source or null if
10972
+ * no SourceHandler supports the source
10973
+ */
10974
+ _Tech.selectSourceHandler = function (source, options) {
10975
+ var handlers = _Tech.sourceHandlers || [];
10976
+ var can = void 0;
10977
+
10978
+ for (var i = 0; i < handlers.length; i++) {
10979
+ can = handlers[i].canHandleSource(source, options);
10980
+
10981
+ if (can) {
10982
+ return handlers[i];
10983
+ }
10984
+ }
10985
+
10986
+ return null;
10987
+ };
10988
+
10989
+ /**
10990
+ * Check if the tech can support the given source.
10991
+ *
10992
+ * @param {Tech~SourceObject} srcObj
10993
+ * The source object
10994
+ *
10995
+ * @param {Object} options
10996
+ * The options passed to the tech
10997
+ *
10998
+ * @return {string}
10999
+ * 'probably', 'maybe', or '' (empty string)
11000
+ */
11001
+ _Tech.canPlaySource = function (srcObj, options) {
11002
+ var sh = _Tech.selectSourceHandler(srcObj, options);
11003
+
11004
+ if (sh) {
11005
+ return sh.canHandleSource(srcObj, options);
11006
+ }
11007
+
11008
+ return '';
11009
+ };
11010
+
11011
+ /**
11012
+ * When using a source handler, prefer its implementation of
11013
+ * any function normally provided by the tech.
11014
+ */
11015
+ var deferrable = ['seekable', 'seeking', 'duration'];
11016
+
11017
+ /**
11018
+ * A wrapper around {@link Tech#seekable} that will call a `SourceHandler`s seekable
11019
+ * function if it exists, with a fallback to the Techs seekable function.
11020
+ *
11021
+ * @method _Tech.seekable
11022
+ */
11023
+
11024
+ /**
11025
+ * A wrapper around {@link Tech#duration} that will call a `SourceHandler`s duration
11026
+ * function if it exists, otherwise it will fallback to the techs duration function.
11027
+ *
11028
+ * @method _Tech.duration
11029
+ */
11030
+
11031
+ deferrable.forEach(function (fnName) {
11032
+ var originalFn = this[fnName];
11033
+
11034
+ if (typeof originalFn !== 'function') {
11035
+ return;
11036
+ }
11037
+
11038
+ this[fnName] = function () {
11039
+ if (this.sourceHandler_ && this.sourceHandler_[fnName]) {
11040
+ return this.sourceHandler_[fnName].apply(this.sourceHandler_, arguments);
11041
+ }
11042
+ return originalFn.apply(this, arguments);
11043
+ };
11044
+ }, _Tech.prototype);
11045
+
11046
+ /**
11047
+ * Create a function for setting the source using a source object
11048
+ * and source handlers.
11049
+ * Should never be called unless a source handler was found.
11050
+ *
11051
+ * @param {Tech~SourceObject} source
11052
+ * A source object with src and type keys
11053
+ */
11054
+ _Tech.prototype.setSource = function (source) {
11055
+ var sh = _Tech.selectSourceHandler(source, this.options_);
11056
+
11057
+ if (!sh) {
11058
+ // Fall back to a native source hander when unsupported sources are
11059
+ // deliberately set
11060
+ if (_Tech.nativeSourceHandler) {
11061
+ sh = _Tech.nativeSourceHandler;
11062
+ } else {
11063
+ log$1.error('No source handler found for the current source.');
11064
+ }
11065
+ }
11066
+
11067
+ // Dispose any existing source handler
11068
+ this.disposeSourceHandler();
11069
+ this.off('dispose', this.disposeSourceHandler);
11070
+
11071
+ if (sh !== _Tech.nativeSourceHandler) {
11072
+ this.currentSource_ = source;
11073
+ }
11074
+
11075
+ this.sourceHandler_ = sh.handleSource(source, this, this.options_);
11076
+ this.on('dispose', this.disposeSourceHandler);
11077
+ };
11078
+
11079
+ /**
11080
+ * Clean up any existing SourceHandlers and listeners when the Tech is disposed.
11081
+ *
11082
+ * @listens Tech#dispose
11083
+ */
11084
+ _Tech.prototype.disposeSourceHandler = function () {
11085
+ // if we have a source and get another one
11086
+ // then we are loading something new
11087
+ // than clear all of our current tracks
11088
+ if (this.currentSource_) {
11089
+ this.clearTracks(['audio', 'video']);
11090
+ this.currentSource_ = null;
11091
+ }
11092
+
11093
+ // always clean up auto-text tracks
11094
+ this.cleanupAutoTextTracks();
11095
+
11096
+ if (this.sourceHandler_) {
11097
+
11098
+ if (this.sourceHandler_.dispose) {
11099
+ this.sourceHandler_.dispose();
11100
+ }
11101
+
11102
+ this.sourceHandler_ = null;
11103
+ }
11104
+ };
11105
+ };
11106
+
11107
+ // The base Tech class needs to be registered as a Component. It is the only
11108
+ // Tech that can be registered as a Component.
11109
+ Component.registerComponent('Tech', Tech);
11110
+ Tech.registerTech('Tech', Tech);
11111
+
11112
+ /**
11113
+ * A list of techs that should be added to techOrder on Players
11114
+ *
11115
+ * @private
11116
+ */
11117
+ Tech.defaultTechOrder_ = [];
11118
+
11119
+ var middlewares = {};
11120
+ var middlewareInstances = {};
11121
+
11122
+ var TERMINATOR = {};
11123
+
11124
+ function use(type, middleware) {
11125
+ middlewares[type] = middlewares[type] || [];
11126
+ middlewares[type].push(middleware);
11127
+ }
11128
+
11129
+ function setSource(player, src, next) {
11130
+ player.setTimeout(function () {
11131
+ return setSourceHelper(src, middlewares[src.type], next, player);
11132
+ }, 1);
11133
+ }
11134
+
11135
+ function setTech(middleware, tech) {
11136
+ middleware.forEach(function (mw) {
11137
+ return mw.setTech && mw.setTech(tech);
11138
+ });
11139
+ }
11140
+
11141
+ /**
11142
+ * Calls a getter on the tech first, through each middleware
11143
+ * from right to left to the player.
11144
+ */
11145
+ function get$1(middleware, tech, method) {
11146
+ return middleware.reduceRight(middlewareIterator(method), tech[method]());
11147
+ }
11148
+
11149
+ /**
11150
+ * Takes the argument given to the player and calls the setter method on each
11151
+ * middleware from left to right to the tech.
11152
+ */
11153
+ function set$1(middleware, tech, method, arg) {
11154
+ return tech[method](middleware.reduce(middlewareIterator(method), arg));
11155
+ }
11156
+
11157
+ /**
11158
+ * Takes the argument given to the player and calls the `call` version of the method
11159
+ * on each middleware from left to right.
11160
+ * Then, call the passed in method on the tech and return the result unchanged
11161
+ * back to the player, through middleware, this time from right to left.
11162
+ */
11163
+ function mediate(middleware, tech, method) {
11164
+ var arg = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : null;
11165
+
11166
+ var callMethod = 'call' + toTitleCase(method);
11167
+ var middlewareValue = middleware.reduce(middlewareIterator(callMethod), arg);
11168
+ var terminated = middlewareValue === TERMINATOR;
11169
+ var returnValue = terminated ? null : tech[method](middlewareValue);
11170
+
11171
+ executeRight(middleware, method, returnValue, terminated);
11172
+
11173
+ return returnValue;
11174
+ }
11175
+
11176
+ var allowedGetters = {
11177
+ buffered: 1,
11178
+ currentTime: 1,
11179
+ duration: 1,
11180
+ seekable: 1,
11181
+ played: 1,
11182
+ paused: 1
11183
+ };
11184
+
11185
+ var allowedSetters = {
11186
+ setCurrentTime: 1
11187
+ };
11188
+
11189
+ var allowedMediators = {
11190
+ play: 1,
11191
+ pause: 1
11192
+ };
11193
+
11194
+ function middlewareIterator(method) {
11195
+ return function (value, mw) {
11196
+ // if the previous middleware terminated, pass along the termination
11197
+ if (value === TERMINATOR) {
11198
+ return TERMINATOR;
11199
+ }
11200
+
11201
+ if (mw[method]) {
11202
+ return mw[method](value);
11203
+ }
11204
+
11205
+ return value;
11206
+ };
11207
+ }
11208
+
11209
+ function executeRight(mws, method, value, terminated) {
11210
+ for (var i = mws.length - 1; i >= 0; i--) {
11211
+ var mw = mws[i];
11212
+
11213
+ if (mw[method]) {
11214
+ mw[method](terminated, value);
11215
+ }
11216
+ }
11217
+ }
11218
+
11219
+ function clearCacheForPlayer(player) {
11220
+ middlewareInstances[player.id()] = null;
11221
+ }
11222
+
11223
+ /**
11224
+ * {
11225
+ * [playerId]: [[mwFactory, mwInstance], ...]
11226
+ * }
11227
+ */
11228
+ function getOrCreateFactory(player, mwFactory) {
11229
+ var mws = middlewareInstances[player.id()];
11230
+ var mw = null;
11231
+
11232
+ if (mws === undefined || mws === null) {
11233
+ mw = mwFactory(player);
11234
+ middlewareInstances[player.id()] = [[mwFactory, mw]];
11235
+ return mw;
11236
+ }
11237
+
11238
+ for (var i = 0; i < mws.length; i++) {
11239
+ var _mws$i = mws[i],
11240
+ mwf = _mws$i[0],
11241
+ mwi = _mws$i[1];
11242
+
11243
+
11244
+ if (mwf !== mwFactory) {
11245
+ continue;
11246
+ }
11247
+
11248
+ mw = mwi;
11249
+ }
11250
+
11251
+ if (mw === null) {
11252
+ mw = mwFactory(player);
11253
+ mws.push([mwFactory, mw]);
11254
+ }
11255
+
11256
+ return mw;
11257
+ }
11258
+
11259
+ function setSourceHelper() {
11260
+ var src = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
11261
+ var middleware = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : [];
11262
+ var next = arguments[2];
11263
+ var player = arguments[3];
11264
+ var acc = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : [];
11265
+ var lastRun = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : false;
11266
+ var mwFactory = middleware[0],
11267
+ mwrest = middleware.slice(1);
11268
+
11269
+ // if mwFactory is a string, then we're at a fork in the road
11270
+
11271
+ if (typeof mwFactory === 'string') {
11272
+ setSourceHelper(src, middlewares[mwFactory], next, player, acc, lastRun);
11273
+
11274
+ // if we have an mwFactory, call it with the player to get the mw,
11275
+ // then call the mw's setSource method
11276
+ } else if (mwFactory) {
11277
+ var mw = getOrCreateFactory(player, mwFactory);
11278
+
11279
+ // if setSource isn't present, implicitly select this middleware
11280
+ if (!mw.setSource) {
11281
+ acc.push(mw);
11282
+ return setSourceHelper(src, mwrest, next, player, acc, lastRun);
11283
+ }
11284
+
11285
+ mw.setSource(assign({}, src), function (err, _src) {
11286
+
11287
+ // something happened, try the next middleware on the current level
11288
+ // make sure to use the old src
11289
+ if (err) {
11290
+ return setSourceHelper(src, mwrest, next, player, acc, lastRun);
11291
+ }
11292
+
11293
+ // we've succeeded, now we need to go deeper
11294
+ acc.push(mw);
11295
+
11296
+ // if it's the same type, continue down the current chain
11297
+ // otherwise, we want to go down the new chain
11298
+ setSourceHelper(_src, src.type === _src.type ? mwrest : middlewares[_src.type], next, player, acc, lastRun);
11299
+ });
11300
+ } else if (mwrest.length) {
11301
+ setSourceHelper(src, mwrest, next, player, acc, lastRun);
11302
+ } else if (lastRun) {
11303
+ next(src, acc);
11304
+ } else {
11305
+ setSourceHelper(src, middlewares['*'], next, player, acc, true);
11306
+ }
11307
+ }
11308
+
11309
+ /**
11310
+ * Mimetypes
11311
+ *
11312
+ * @see http://hul.harvard.edu/ois/////systems/wax/wax-public-help/mimetypes.htm
11313
+ * @typedef Mimetypes~Kind
11314
+ * @enum
11315
+ */
11316
+ var MimetypesKind = {
11317
+ opus: 'video/ogg',
11318
+ ogv: 'video/ogg',
11319
+ mp4: 'video/mp4',
11320
+ mov: 'video/mp4',
11321
+ m4v: 'video/mp4',
11322
+ mkv: 'video/x-matroska',
11323
+ mp3: 'audio/mpeg',
11324
+ aac: 'audio/aac',
11325
+ oga: 'audio/ogg',
11326
+ m3u8: 'application/x-mpegURL'
11327
+ };
11328
+
11329
+ /**
11330
+ * Get the mimetype of a given src url if possible
11331
+ *
11332
+ * @param {string} src
11333
+ * The url to the src
11334
+ *
11335
+ * @return {string}
11336
+ * return the mimetype if it was known or empty string otherwise
11337
+ */
11338
+ var getMimetype = function getMimetype() {
11339
+ var src = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
11340
+
11341
+ var ext = getFileExtension(src);
11342
+ var mimetype = MimetypesKind[ext.toLowerCase()];
11343
+
11344
+ return mimetype || '';
11345
+ };
11346
+
11347
+ /**
11348
+ * Find the mime type of a given source string if possible. Uses the player
11349
+ * source cache.
11350
+ *
11351
+ * @param {Player} player
11352
+ * The player object
11353
+ *
11354
+ * @param {string} src
11355
+ * The source string
11356
+ *
11357
+ * @return {string}
11358
+ * The type that was found
11359
+ */
11360
+ var findMimetype = function findMimetype(player, src) {
11361
+ if (!src) {
11362
+ return '';
11363
+ }
11364
+
11365
+ // 1. check for the type in the `source` cache
11366
+ if (player.cache_.source.src === src && player.cache_.source.type) {
11367
+ return player.cache_.source.type;
11368
+ }
11369
+
11370
+ // 2. see if we have this source in our `currentSources` cache
11371
+ var matchingSources = player.cache_.sources.filter(function (s) {
11372
+ return s.src === src;
11373
+ });
11374
+
11375
+ if (matchingSources.length) {
11376
+ return matchingSources[0].type;
11377
+ }
11378
+
11379
+ // 3. look for the src url in source elements and use the type there
11380
+ var sources = player.$$('source');
11381
+
11382
+ for (var i = 0; i < sources.length; i++) {
11383
+ var s = sources[i];
11384
+
11385
+ if (s.type && s.src && s.src === src) {
11386
+ return s.type;
11387
+ }
11388
+ }
11389
+
11390
+ // 4. finally fallback to our list of mime types based on src url extension
11391
+ return getMimetype(src);
11392
+ };
11393
+
11394
+ /**
11395
+ * @module filter-source
11396
+ */
11397
+
11398
+ /**
11399
+ * Filter out single bad source objects or multiple source objects in an
11400
+ * array. Also flattens nested source object arrays into a 1 dimensional
11401
+ * array of source objects.
11402
+ *
11403
+ * @param {Tech~SourceObject|Tech~SourceObject[]} src
11404
+ * The src object to filter
11405
+ *
11406
+ * @return {Tech~SourceObject[]}
11407
+ * An array of sourceobjects containing only valid sources
11408
+ *
11409
+ * @private
11410
+ */
11411
+ var filterSource = function filterSource(src) {
11412
+ // traverse array
11413
+ if (Array.isArray(src)) {
11414
+ var newsrc = [];
11415
+
11416
+ src.forEach(function (srcobj) {
11417
+ srcobj = filterSource(srcobj);
11418
+
11419
+ if (Array.isArray(srcobj)) {
11420
+ newsrc = newsrc.concat(srcobj);
11421
+ } else if (isObject(srcobj)) {
11422
+ newsrc.push(srcobj);
11423
+ }
11424
+ });
11425
+
11426
+ src = newsrc;
11427
+ } else if (typeof src === 'string' && src.trim()) {
11428
+ // convert string into object
11429
+ src = [fixSource({ src: src })];
11430
+ } else if (isObject(src) && typeof src.src === 'string' && src.src && src.src.trim()) {
11431
+ // src is already valid
11432
+ src = [fixSource(src)];
11433
+ } else {
11434
+ // invalid source, turn it into an empty array
11435
+ src = [];
11436
+ }
11437
+
11438
+ return src;
11439
+ };
11440
+
11441
+ /**
11442
+ * Checks src mimetype, adding it when possible
11443
+ *
11444
+ * @param {Tech~SourceObject} src
11445
+ * The src object to check
11446
+ * @return {Tech~SourceObject}
11447
+ * src Object with known type
11448
+ */
11449
+ function fixSource(src) {
11450
+ var mimetype = getMimetype(src.src);
11451
+
11452
+ if (!src.type && mimetype) {
11453
+ src.type = mimetype;
11454
+ }
11455
+
11456
+ return src;
11457
+ }
11458
+
11459
+ /**
11460
+ * @file loader.js
11461
+ */
11462
+
11463
+ /**
11464
+ * The `MediaLoader` is the `Component` that decides which playback technology to load
11465
+ * when a player is initialized.
11466
+ *
11467
+ * @extends Component
11468
+ */
11469
+
11470
+ var MediaLoader = function (_Component) {
11471
+ inherits(MediaLoader, _Component);
11472
+
11473
+ /**
11474
+ * Create an instance of this class.
11475
+ *
11476
+ * @param {Player} player
11477
+ * The `Player` that this class should attach to.
11478
+ *
11479
+ * @param {Object} [options]
11480
+ * The key/value store of player options.
11481
+ *
11482
+ * @param {Component~ReadyCallback} [ready]
11483
+ * The function that is run when this component is ready.
11484
+ */
11485
+ function MediaLoader(player, options, ready) {
11486
+ classCallCheck(this, MediaLoader);
11487
+
11488
+ // MediaLoader has no element
11489
+ var options_ = mergeOptions({ createEl: false }, options);
11490
+
11491
+ // If there are no sources when the player is initialized,
11492
+ // load the first supported playback technology.
11493
+
11494
+ var _this = possibleConstructorReturn(this, _Component.call(this, player, options_, ready));
11495
+
11496
+ if (!options.playerOptions.sources || options.playerOptions.sources.length === 0) {
11497
+ for (var i = 0, j = options.playerOptions.techOrder; i < j.length; i++) {
11498
+ var techName = toTitleCase(j[i]);
11499
+ var tech = Tech.getTech(techName);
11500
+
11501
+ // Support old behavior of techs being registered as components.
11502
+ // Remove once that deprecated behavior is removed.
11503
+ if (!techName) {
11504
+ tech = Component.getComponent(techName);
11505
+ }
11506
+
11507
+ // Check if the browser supports this technology
11508
+ if (tech && tech.isSupported()) {
11509
+ player.loadTech_(techName);
11510
+ break;
11511
+ }
11512
+ }
11513
+ } else {
11514
+ // Loop through playback technologies (HTML5, Flash) and check for support.
11515
+ // Then load the best source.
11516
+ // A few assumptions here:
11517
+ // All playback technologies respect preload false.
11518
+ player.src(options.playerOptions.sources);
11519
+ }
11520
+ return _this;
11521
+ }
11522
+
11523
+ return MediaLoader;
11524
+ }(Component);
11525
+
11526
+ Component.registerComponent('MediaLoader', MediaLoader);
11527
+
11528
+ /**
11529
+ * @file clickable-component.js
11530
+ */
11531
+
11532
+ /**
11533
+ * Clickable Component which is clickable or keyboard actionable,
11534
+ * but is not a native HTML button.
11535
+ *
11536
+ * @extends Component
11537
+ */
11538
+
11539
+ var ClickableComponent = function (_Component) {
11540
+ inherits(ClickableComponent, _Component);
11541
+
11542
+ /**
11543
+ * Creates an instance of this class.
11544
+ *
11545
+ * @param {Player} player
11546
+ * The `Player` that this class should be attached to.
11547
+ *
11548
+ * @param {Object} [options]
11549
+ * The key/value store of player options.
11550
+ */
11551
+ function ClickableComponent(player, options) {
11552
+ classCallCheck(this, ClickableComponent);
11553
+
11554
+ var _this = possibleConstructorReturn(this, _Component.call(this, player, options));
11555
+
11556
+ _this.emitTapEvents();
11557
+
11558
+ _this.enable();
11559
+ return _this;
11560
+ }
11561
+
11562
+ /**
11563
+ * Create the `Component`s DOM element.
11564
+ *
11565
+ * @param {string} [tag=div]
11566
+ * The element's node type.
11567
+ *
11568
+ * @param {Object} [props={}]
11569
+ * An object of properties that should be set on the element.
11570
+ *
11571
+ * @param {Object} [attributes={}]
11572
+ * An object of attributes that should be set on the element.
11573
+ *
11574
+ * @return {Element}
11575
+ * The element that gets created.
11576
+ */
11577
+
11578
+
11579
+ ClickableComponent.prototype.createEl = function createEl$$1() {
11580
+ var tag = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'div';
11581
+ var props = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
11582
+ var attributes = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
11583
+
11584
+ props = assign({
11585
+ innerHTML: '<span aria-hidden="true" class="vjs-icon-placeholder"></span>',
11586
+ className: this.buildCSSClass(),
11587
+ tabIndex: 0
11588
+ }, props);
11589
+
11590
+ if (tag === 'button') {
11591
+ log$1.error('Creating a ClickableComponent with an HTML element of ' + tag + ' is not supported; use a Button instead.');
11592
+ }
11593
+
11594
+ // Add ARIA attributes for clickable element which is not a native HTML button
11595
+ attributes = assign({
11596
+ role: 'button'
11597
+ }, attributes);
11598
+
11599
+ this.tabIndex_ = props.tabIndex;
11600
+
11601
+ var el = _Component.prototype.createEl.call(this, tag, props, attributes);
11602
+
11603
+ this.createControlTextEl(el);
11604
+
11605
+ return el;
11606
+ };
11607
+
11608
+ ClickableComponent.prototype.dispose = function dispose() {
11609
+ // remove controlTextEl_ on dispose
11610
+ this.controlTextEl_ = null;
11611
+
11612
+ _Component.prototype.dispose.call(this);
11613
+ };
11614
+
11615
+ /**
11616
+ * Create a control text element on this `Component`
11617
+ *
11618
+ * @param {Element} [el]
11619
+ * Parent element for the control text.
11620
+ *
11621
+ * @return {Element}
11622
+ * The control text element that gets created.
11623
+ */
11624
+
11625
+
11626
+ ClickableComponent.prototype.createControlTextEl = function createControlTextEl(el) {
11627
+ this.controlTextEl_ = createEl('span', {
11628
+ className: 'vjs-control-text'
11629
+ }, {
11630
+ // let the screen reader user know that the text of the element may change
11631
+ 'aria-live': 'polite'
11632
+ });
11633
+
11634
+ if (el) {
11635
+ el.appendChild(this.controlTextEl_);
11636
+ }
11637
+
11638
+ this.controlText(this.controlText_, el);
11639
+
11640
+ return this.controlTextEl_;
11641
+ };
11642
+
11643
+ /**
11644
+ * Get or set the localize text to use for the controls on the `Component`.
11645
+ *
11646
+ * @param {string} [text]
11647
+ * Control text for element.
11648
+ *
11649
+ * @param {Element} [el=this.el()]
11650
+ * Element to set the title on.
11651
+ *
11652
+ * @return {string}
11653
+ * - The control text when getting
11654
+ */
11655
+
11656
+
11657
+ ClickableComponent.prototype.controlText = function controlText(text) {
11658
+ var el = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : this.el();
11659
+
11660
+ if (text === undefined) {
11661
+ return this.controlText_ || 'Need Text';
11662
+ }
11663
+
11664
+ var localizedText = this.localize(text);
11665
+
11666
+ this.controlText_ = text;
11667
+ textContent(this.controlTextEl_, localizedText);
11668
+ if (!this.nonIconControl) {
11669
+ // Set title attribute if only an icon is shown
11670
+ el.setAttribute('title', localizedText);
11671
+ }
11672
+ };
11673
+
11674
+ /**
11675
+ * Builds the default DOM `className`.
11676
+ *
11677
+ * @return {string}
11678
+ * The DOM `className` for this object.
11679
+ */
11680
+
11681
+
11682
+ ClickableComponent.prototype.buildCSSClass = function buildCSSClass() {
11683
+ return 'vjs-control vjs-button ' + _Component.prototype.buildCSSClass.call(this);
11684
+ };
11685
+
11686
+ /**
11687
+ * Enable this `Component`s element.
11688
+ */
11689
+
11690
+
11691
+ ClickableComponent.prototype.enable = function enable() {
11692
+ if (!this.enabled_) {
11693
+ this.enabled_ = true;
11694
+ this.removeClass('vjs-disabled');
11695
+ this.el_.setAttribute('aria-disabled', 'false');
11696
+ if (typeof this.tabIndex_ !== 'undefined') {
11697
+ this.el_.setAttribute('tabIndex', this.tabIndex_);
11698
+ }
11699
+ this.on(['tap', 'click'], this.handleClick);
11700
+ this.on('focus', this.handleFocus);
11701
+ this.on('blur', this.handleBlur);
11702
+ }
11703
+ };
11704
+
11705
+ /**
11706
+ * Disable this `Component`s element.
11707
+ */
11708
+
11709
+
11710
+ ClickableComponent.prototype.disable = function disable() {
11711
+ this.enabled_ = false;
11712
+ this.addClass('vjs-disabled');
11713
+ this.el_.setAttribute('aria-disabled', 'true');
11714
+ if (typeof this.tabIndex_ !== 'undefined') {
11715
+ this.el_.removeAttribute('tabIndex');
11716
+ }
11717
+ this.off(['tap', 'click'], this.handleClick);
11718
+ this.off('focus', this.handleFocus);
11719
+ this.off('blur', this.handleBlur);
11720
+ };
11721
+
11722
+ /**
11723
+ * This gets called when a `ClickableComponent` gets:
11724
+ * - Clicked (via the `click` event, listening starts in the constructor)
11725
+ * - Tapped (via the `tap` event, listening starts in the constructor)
11726
+ * - The following things happen in order:
11727
+ * 1. {@link ClickableComponent#handleFocus} is called via a `focus` event on the
11728
+ * `ClickableComponent`.
11729
+ * 2. {@link ClickableComponent#handleFocus} adds a listener for `keydown` on using
11730
+ * {@link ClickableComponent#handleKeyPress}.
11731
+ * 3. `ClickableComponent` has not had a `blur` event (`blur` means that focus was lost). The user presses
11732
+ * the space or enter key.
11733
+ * 4. {@link ClickableComponent#handleKeyPress} calls this function with the `keydown`
11734
+ * event as a parameter.
11735
+ *
11736
+ * @param {EventTarget~Event} event
11737
+ * The `keydown`, `tap`, or `click` event that caused this function to be
11738
+ * called.
11739
+ *
11740
+ * @listens tap
11741
+ * @listens click
11742
+ * @abstract
11743
+ */
11744
+
11745
+
11746
+ ClickableComponent.prototype.handleClick = function handleClick(event) {};
11747
+
11748
+ /**
11749
+ * This gets called when a `ClickableComponent` gains focus via a `focus` event.
11750
+ * Turns on listening for `keydown` events. When they happen it
11751
+ * calls `this.handleKeyPress`.
11752
+ *
11753
+ * @param {EventTarget~Event} event
11754
+ * The `focus` event that caused this function to be called.
11755
+ *
11756
+ * @listens focus
11757
+ */
11758
+
11759
+
11760
+ ClickableComponent.prototype.handleFocus = function handleFocus(event) {
11761
+ on(document_1, 'keydown', bind(this, this.handleKeyPress));
11762
+ };
11763
+
11764
+ /**
11765
+ * Called when this ClickableComponent has focus and a key gets pressed down. By
11766
+ * default it will call `this.handleClick` when the key is space or enter.
11767
+ *
11768
+ * @param {EventTarget~Event} event
11769
+ * The `keydown` event that caused this function to be called.
11770
+ *
11771
+ * @listens keydown
11772
+ */
11773
+
11774
+
11775
+ ClickableComponent.prototype.handleKeyPress = function handleKeyPress(event) {
11776
+
11777
+ // Support Space (32) or Enter (13) key operation to fire a click event
11778
+ if (event.which === 32 || event.which === 13) {
11779
+ event.preventDefault();
11780
+ this.trigger('click');
11781
+ } else if (_Component.prototype.handleKeyPress) {
11782
+
11783
+ // Pass keypress handling up for unsupported keys
11784
+ _Component.prototype.handleKeyPress.call(this, event);
11785
+ }
11786
+ };
11787
+
11788
+ /**
11789
+ * Called when a `ClickableComponent` loses focus. Turns off the listener for
11790
+ * `keydown` events. Which Stops `this.handleKeyPress` from getting called.
11791
+ *
11792
+ * @param {EventTarget~Event} event
11793
+ * The `blur` event that caused this function to be called.
11794
+ *
11795
+ * @listens blur
11796
+ */
11797
+
11798
+
11799
+ ClickableComponent.prototype.handleBlur = function handleBlur(event) {
11800
+ off(document_1, 'keydown', bind(this, this.handleKeyPress));
11801
+ };
11802
+
11803
+ return ClickableComponent;
11804
+ }(Component);
11805
+
11806
+ Component.registerComponent('ClickableComponent', ClickableComponent);
11807
+
11808
+ /**
11809
+ * @file poster-image.js
11810
+ */
11811
+
11812
+ /**
11813
+ * A `ClickableComponent` that handles showing the poster image for the player.
11814
+ *
11815
+ * @extends ClickableComponent
11816
+ */
11817
+
11818
+ var PosterImage = function (_ClickableComponent) {
11819
+ inherits(PosterImage, _ClickableComponent);
11820
+
11821
+ /**
11822
+ * Create an instance of this class.
11823
+ *
11824
+ * @param {Player} player
11825
+ * The `Player` that this class should attach to.
11826
+ *
11827
+ * @param {Object} [options]
11828
+ * The key/value store of player options.
11829
+ */
11830
+ function PosterImage(player, options) {
11831
+ classCallCheck(this, PosterImage);
11832
+
11833
+ var _this = possibleConstructorReturn(this, _ClickableComponent.call(this, player, options));
11834
+
11835
+ _this.update();
11836
+ player.on('posterchange', bind(_this, _this.update));
11837
+ return _this;
11838
+ }
11839
+
11840
+ /**
11841
+ * Clean up and dispose of the `PosterImage`.
11842
+ */
11843
+
11844
+
11845
+ PosterImage.prototype.dispose = function dispose() {
11846
+ this.player().off('posterchange', this.update);
11847
+ _ClickableComponent.prototype.dispose.call(this);
11848
+ };
11849
+
11850
+ /**
11851
+ * Create the `PosterImage`s DOM element.
11852
+ *
11853
+ * @return {Element}
11854
+ * The element that gets created.
11855
+ */
11856
+
11857
+
11858
+ PosterImage.prototype.createEl = function createEl$$1() {
11859
+ var el = createEl('div', {
11860
+ className: 'vjs-poster',
11861
+
11862
+ // Don't want poster to be tabbable.
11863
+ tabIndex: -1
11864
+ });
11865
+
11866
+ return el;
11867
+ };
11868
+
11869
+ /**
11870
+ * An {@link EventTarget~EventListener} for {@link Player#posterchange} events.
11871
+ *
11872
+ * @listens Player#posterchange
11873
+ *
11874
+ * @param {EventTarget~Event} [event]
11875
+ * The `Player#posterchange` event that triggered this function.
11876
+ */
11877
+
11878
+
11879
+ PosterImage.prototype.update = function update(event) {
11880
+ var url = this.player().poster();
11881
+
11882
+ this.setSrc(url);
11883
+
11884
+ // If there's no poster source we should display:none on this component
11885
+ // so it's not still clickable or right-clickable
11886
+ if (url) {
11887
+ this.show();
11888
+ } else {
11889
+ this.hide();
11890
+ }
11891
+ };
11892
+
11893
+ /**
11894
+ * Set the source of the `PosterImage` depending on the display method.
11895
+ *
11896
+ * @param {string} url
11897
+ * The URL to the source for the `PosterImage`.
11898
+ */
11899
+
11900
+
11901
+ PosterImage.prototype.setSrc = function setSrc(url) {
11902
+ var backgroundImage = '';
11903
+
11904
+ // Any falsy value should stay as an empty string, otherwise
11905
+ // this will throw an extra error
11906
+ if (url) {
11907
+ backgroundImage = 'url("' + url + '")';
11908
+ }
11909
+
11910
+ this.el_.style.backgroundImage = backgroundImage;
11911
+ };
11912
+
11913
+ /**
11914
+ * An {@link EventTarget~EventListener} for clicks on the `PosterImage`. See
11915
+ * {@link ClickableComponent#handleClick} for instances where this will be triggered.
11916
+ *
11917
+ * @listens tap
11918
+ * @listens click
11919
+ * @listens keydown
11920
+ *
11921
+ * @param {EventTarget~Event} event
11922
+ + The `click`, `tap` or `keydown` event that caused this function to be called.
11923
+ */
11924
+
11925
+
11926
+ PosterImage.prototype.handleClick = function handleClick(event) {
11927
+ // We don't want a click to trigger playback when controls are disabled
11928
+ if (!this.player_.controls()) {
11929
+ return;
11930
+ }
11931
+
11932
+ if (this.player_.paused()) {
11933
+ silencePromise(this.player_.play());
11934
+ } else {
11935
+ this.player_.pause();
11936
+ }
11937
+ };
11938
+
11939
+ return PosterImage;
11940
+ }(ClickableComponent);
11941
+
11942
+ Component.registerComponent('PosterImage', PosterImage);
11943
+
11944
+ /**
11945
+ * @file text-track-display.js
11946
+ */
11947
+
11948
+ var darkGray = '#222';
11949
+ var lightGray = '#ccc';
11950
+ var fontMap = {
11951
+ monospace: 'monospace',
11952
+ sansSerif: 'sans-serif',
11953
+ serif: 'serif',
11954
+ monospaceSansSerif: '"Andale Mono", "Lucida Console", monospace',
11955
+ monospaceSerif: '"Courier New", monospace',
11956
+ proportionalSansSerif: 'sans-serif',
11957
+ proportionalSerif: 'serif',
11958
+ casual: '"Comic Sans MS", Impact, fantasy',
11959
+ script: '"Monotype Corsiva", cursive',
11960
+ smallcaps: '"Andale Mono", "Lucida Console", monospace, sans-serif'
11961
+ };
11962
+
11963
+ /**
11964
+ * Construct an rgba color from a given hex color code.
11965
+ *
11966
+ * @param {number} color
11967
+ * Hex number for color, like #f0e or #f604e2.
11968
+ *
11969
+ * @param {number} opacity
11970
+ * Value for opacity, 0.0 - 1.0.
11971
+ *
11972
+ * @return {string}
11973
+ * The rgba color that was created, like 'rgba(255, 0, 0, 0.3)'.
11974
+ */
11975
+ function constructColor(color, opacity) {
11976
+ var hex = void 0;
11977
+
11978
+ if (color.length === 4) {
11979
+ // color looks like "#f0e"
11980
+ hex = color[1] + color[1] + color[2] + color[2] + color[3] + color[3];
11981
+ } else if (color.length === 7) {
11982
+ // color looks like "#f604e2"
11983
+ hex = color.slice(1);
11984
+ } else {
11985
+ throw new Error('Invalid color code provided, ' + color + '; must be formatted as e.g. #f0e or #f604e2.');
11986
+ }
11987
+ return 'rgba(' + parseInt(hex.slice(0, 2), 16) + ',' + parseInt(hex.slice(2, 4), 16) + ',' + parseInt(hex.slice(4, 6), 16) + ',' + opacity + ')';
11988
+ }
11989
+
11990
+ /**
11991
+ * Try to update the style of a DOM element. Some style changes will throw an error,
11992
+ * particularly in IE8. Those should be noops.
11993
+ *
11994
+ * @param {Element} el
11995
+ * The DOM element to be styled.
11996
+ *
11997
+ * @param {string} style
11998
+ * The CSS property on the element that should be styled.
11999
+ *
12000
+ * @param {string} rule
12001
+ * The style rule that should be applied to the property.
12002
+ *
12003
+ * @private
12004
+ */
12005
+ function tryUpdateStyle(el, style, rule) {
12006
+ try {
12007
+ el.style[style] = rule;
12008
+ } catch (e) {
12009
+
12010
+ // Satisfies linter.
12011
+ return;
12012
+ }
12013
+ }
12014
+
12015
+ /**
12016
+ * The component for displaying text track cues.
12017
+ *
12018
+ * @extends Component
12019
+ */
12020
+
12021
+ var TextTrackDisplay = function (_Component) {
12022
+ inherits(TextTrackDisplay, _Component);
12023
+
12024
+ /**
12025
+ * Creates an instance of this class.
12026
+ *
12027
+ * @param {Player} player
12028
+ * The `Player` that this class should be attached to.
12029
+ *
12030
+ * @param {Object} [options]
12031
+ * The key/value store of player options.
12032
+ *
12033
+ * @param {Component~ReadyCallback} [ready]
12034
+ * The function to call when `TextTrackDisplay` is ready.
12035
+ */
12036
+ function TextTrackDisplay(player, options, ready) {
12037
+ classCallCheck(this, TextTrackDisplay);
12038
+
12039
+ var _this = possibleConstructorReturn(this, _Component.call(this, player, options, ready));
12040
+
12041
+ player.on('loadstart', bind(_this, _this.toggleDisplay));
12042
+ player.on('texttrackchange', bind(_this, _this.updateDisplay));
12043
+ player.on('loadstart', bind(_this, _this.preselectTrack));
12044
+
12045
+ // This used to be called during player init, but was causing an error
12046
+ // if a track should show by default and the display hadn't loaded yet.
12047
+ // Should probably be moved to an external track loader when we support
12048
+ // tracks that don't need a display.
12049
+ player.ready(bind(_this, function () {
12050
+ if (player.tech_ && player.tech_.featuresNativeTextTracks) {
12051
+ this.hide();
12052
+ return;
12053
+ }
12054
+
12055
+ player.on('fullscreenchange', bind(this, this.updateDisplay));
12056
+
12057
+ var tracks = this.options_.playerOptions.tracks || [];
12058
+
12059
+ for (var i = 0; i < tracks.length; i++) {
12060
+ this.player_.addRemoteTextTrack(tracks[i], true);
12061
+ }
12062
+
12063
+ this.preselectTrack();
12064
+ }));
12065
+ return _this;
12066
+ }
12067
+
12068
+ /**
12069
+ * Preselect a track following this precedence:
12070
+ * - matches the previously selected {@link TextTrack}'s language and kind
12071
+ * - matches the previously selected {@link TextTrack}'s language only
12072
+ * - is the first default captions track
12073
+ * - is the first default descriptions track
12074
+ *
12075
+ * @listens Player#loadstart
12076
+ */
12077
+
12078
+
12079
+ TextTrackDisplay.prototype.preselectTrack = function preselectTrack() {
12080
+ var modes = { captions: 1, subtitles: 1 };
12081
+ var trackList = this.player_.textTracks();
12082
+ var userPref = this.player_.cache_.selectedLanguage;
12083
+ var firstDesc = void 0;
12084
+ var firstCaptions = void 0;
12085
+ var preferredTrack = void 0;
12086
+
12087
+ for (var i = 0; i < trackList.length; i++) {
12088
+ var track = trackList[i];
12089
+
12090
+ if (userPref && userPref.enabled && userPref.language === track.language) {
12091
+ // Always choose the track that matches both language and kind
12092
+ if (track.kind === userPref.kind) {
12093
+ preferredTrack = track;
12094
+ // or choose the first track that matches language
12095
+ } else if (!preferredTrack) {
12096
+ preferredTrack = track;
12097
+ }
12098
+
12099
+ // clear everything if offTextTrackMenuItem was clicked
12100
+ } else if (userPref && !userPref.enabled) {
12101
+ preferredTrack = null;
12102
+ firstDesc = null;
12103
+ firstCaptions = null;
12104
+ } else if (track.default) {
12105
+ if (track.kind === 'descriptions' && !firstDesc) {
12106
+ firstDesc = track;
12107
+ } else if (track.kind in modes && !firstCaptions) {
12108
+ firstCaptions = track;
12109
+ }
12110
+ }
12111
+ }
12112
+
12113
+ // The preferredTrack matches the user preference and takes
12114
+ // precedence over all the other tracks.
12115
+ // So, display the preferredTrack before the first default track
12116
+ // and the subtitles/captions track before the descriptions track
12117
+ if (preferredTrack) {
12118
+ preferredTrack.mode = 'showing';
12119
+ } else if (firstCaptions) {
12120
+ firstCaptions.mode = 'showing';
12121
+ } else if (firstDesc) {
12122
+ firstDesc.mode = 'showing';
12123
+ }
12124
+ };
12125
+
12126
+ /**
12127
+ * Turn display of {@link TextTrack}'s from the current state into the other state.
12128
+ * There are only two states:
12129
+ * - 'shown'
12130
+ * - 'hidden'
12131
+ *
12132
+ * @listens Player#loadstart
12133
+ */
12134
+
12135
+
12136
+ TextTrackDisplay.prototype.toggleDisplay = function toggleDisplay() {
12137
+ if (this.player_.tech_ && this.player_.tech_.featuresNativeTextTracks) {
12138
+ this.hide();
12139
+ } else {
12140
+ this.show();
12141
+ }
12142
+ };
12143
+
12144
+ /**
12145
+ * Create the {@link Component}'s DOM element.
12146
+ *
12147
+ * @return {Element}
12148
+ * The element that was created.
12149
+ */
12150
+
12151
+
12152
+ TextTrackDisplay.prototype.createEl = function createEl() {
12153
+ return _Component.prototype.createEl.call(this, 'div', {
12154
+ className: 'vjs-text-track-display'
12155
+ }, {
12156
+ 'aria-live': 'off',
12157
+ 'aria-atomic': 'true'
12158
+ });
12159
+ };
12160
+
12161
+ /**
12162
+ * Clear all displayed {@link TextTrack}s.
12163
+ */
12164
+
12165
+
12166
+ TextTrackDisplay.prototype.clearDisplay = function clearDisplay() {
12167
+ if (typeof window_1.WebVTT === 'function') {
12168
+ window_1.WebVTT.processCues(window_1, [], this.el_);
12169
+ }
12170
+ };
12171
+
12172
+ /**
12173
+ * Update the displayed TextTrack when a either a {@link Player#texttrackchange} or
12174
+ * a {@link Player#fullscreenchange} is fired.
12175
+ *
12176
+ * @listens Player#texttrackchange
12177
+ * @listens Player#fullscreenchange
12178
+ */
12179
+
12180
+
12181
+ TextTrackDisplay.prototype.updateDisplay = function updateDisplay() {
12182
+ var tracks = this.player_.textTracks();
12183
+
12184
+ this.clearDisplay();
12185
+
12186
+ // Track display prioritization model: if multiple tracks are 'showing',
12187
+ // display the first 'subtitles' or 'captions' track which is 'showing',
12188
+ // otherwise display the first 'descriptions' track which is 'showing'
12189
+
12190
+ var descriptionsTrack = null;
12191
+ var captionsSubtitlesTrack = null;
12192
+ var i = tracks.length;
12193
+
12194
+ while (i--) {
12195
+ var track = tracks[i];
12196
+
12197
+ if (track.mode === 'showing') {
12198
+ if (track.kind === 'descriptions') {
12199
+ descriptionsTrack = track;
12200
+ } else {
12201
+ captionsSubtitlesTrack = track;
12202
+ }
12203
+ }
12204
+ }
12205
+
12206
+ if (captionsSubtitlesTrack) {
12207
+ if (this.getAttribute('aria-live') !== 'off') {
12208
+ this.setAttribute('aria-live', 'off');
12209
+ }
12210
+ this.updateForTrack(captionsSubtitlesTrack);
12211
+ } else if (descriptionsTrack) {
12212
+ if (this.getAttribute('aria-live') !== 'assertive') {
12213
+ this.setAttribute('aria-live', 'assertive');
12214
+ }
12215
+ this.updateForTrack(descriptionsTrack);
12216
+ }
12217
+ };
12218
+
12219
+ /**
12220
+ * Add an {@link TextTrack} to to the {@link Tech}s {@link TextTrackList}.
12221
+ *
12222
+ * @param {TextTrack} track
12223
+ * Text track object to be added to the list.
12224
+ */
12225
+
12226
+
12227
+ TextTrackDisplay.prototype.updateForTrack = function updateForTrack(track) {
12228
+ if (typeof window_1.WebVTT !== 'function' || !track.activeCues) {
12229
+ return;
12230
+ }
12231
+
12232
+ var cues = [];
12233
+
12234
+ for (var _i = 0; _i < track.activeCues.length; _i++) {
12235
+ cues.push(track.activeCues[_i]);
12236
+ }
12237
+
12238
+ window_1.WebVTT.processCues(window_1, cues, this.el_);
12239
+
12240
+ if (!this.player_.textTrackSettings) {
12241
+ return;
12242
+ }
12243
+
12244
+ var overrides = this.player_.textTrackSettings.getValues();
12245
+
12246
+ var i = cues.length;
12247
+
12248
+ while (i--) {
12249
+ var cue = cues[i];
12250
+
12251
+ if (!cue) {
12252
+ continue;
12253
+ }
12254
+
12255
+ var cueDiv = cue.displayState;
12256
+
12257
+ if (overrides.color) {
12258
+ cueDiv.firstChild.style.color = overrides.color;
12259
+ }
12260
+ if (overrides.textOpacity) {
12261
+ tryUpdateStyle(cueDiv.firstChild, 'color', constructColor(overrides.color || '#fff', overrides.textOpacity));
12262
+ }
12263
+ if (overrides.backgroundColor) {
12264
+ cueDiv.firstChild.style.backgroundColor = overrides.backgroundColor;
12265
+ }
12266
+ if (overrides.backgroundOpacity) {
12267
+ tryUpdateStyle(cueDiv.firstChild, 'backgroundColor', constructColor(overrides.backgroundColor || '#000', overrides.backgroundOpacity));
12268
+ }
12269
+ if (overrides.windowColor) {
12270
+ if (overrides.windowOpacity) {
12271
+ tryUpdateStyle(cueDiv, 'backgroundColor', constructColor(overrides.windowColor, overrides.windowOpacity));
12272
+ } else {
12273
+ cueDiv.style.backgroundColor = overrides.windowColor;
12274
+ }
12275
+ }
12276
+ if (overrides.edgeStyle) {
12277
+ if (overrides.edgeStyle === 'dropshadow') {
12278
+ cueDiv.firstChild.style.textShadow = '2px 2px 3px ' + darkGray + ', 2px 2px 4px ' + darkGray + ', 2px 2px 5px ' + darkGray;
12279
+ } else if (overrides.edgeStyle === 'raised') {
12280
+ cueDiv.firstChild.style.textShadow = '1px 1px ' + darkGray + ', 2px 2px ' + darkGray + ', 3px 3px ' + darkGray;
12281
+ } else if (overrides.edgeStyle === 'depressed') {
12282
+ cueDiv.firstChild.style.textShadow = '1px 1px ' + lightGray + ', 0 1px ' + lightGray + ', -1px -1px ' + darkGray + ', 0 -1px ' + darkGray;
12283
+ } else if (overrides.edgeStyle === 'uniform') {
12284
+ cueDiv.firstChild.style.textShadow = '0 0 4px ' + darkGray + ', 0 0 4px ' + darkGray + ', 0 0 4px ' + darkGray + ', 0 0 4px ' + darkGray;
12285
+ }
12286
+ }
12287
+ if (overrides.fontPercent && overrides.fontPercent !== 1) {
12288
+ var fontSize = window_1.parseFloat(cueDiv.style.fontSize);
12289
+
12290
+ cueDiv.style.fontSize = fontSize * overrides.fontPercent + 'px';
12291
+ cueDiv.style.height = 'auto';
12292
+ cueDiv.style.top = 'auto';
12293
+ cueDiv.style.bottom = '2px';
12294
+ }
12295
+ if (overrides.fontFamily && overrides.fontFamily !== 'default') {
12296
+ if (overrides.fontFamily === 'small-caps') {
12297
+ cueDiv.firstChild.style.fontVariant = 'small-caps';
12298
+ } else {
12299
+ cueDiv.firstChild.style.fontFamily = fontMap[overrides.fontFamily];
12300
+ }
12301
+ }
12302
+ }
12303
+ };
12304
+
12305
+ return TextTrackDisplay;
12306
+ }(Component);
12307
+
12308
+ Component.registerComponent('TextTrackDisplay', TextTrackDisplay);
12309
+
12310
+ /**
12311
+ * @file loading-spinner.js
12312
+ */
12313
+
12314
+ /**
12315
+ * A loading spinner for use during waiting/loading events.
12316
+ *
12317
+ * @extends Component
12318
+ */
12319
+
12320
+ var LoadingSpinner = function (_Component) {
12321
+ inherits(LoadingSpinner, _Component);
12322
+
12323
+ function LoadingSpinner() {
12324
+ classCallCheck(this, LoadingSpinner);
12325
+ return possibleConstructorReturn(this, _Component.apply(this, arguments));
12326
+ }
12327
+
12328
+ /**
12329
+ * Create the `LoadingSpinner`s DOM element.
12330
+ *
12331
+ * @return {Element}
12332
+ * The dom element that gets created.
12333
+ */
12334
+ LoadingSpinner.prototype.createEl = function createEl$$1() {
12335
+ var isAudio = this.player_.isAudio();
12336
+ var playerType = this.localize(isAudio ? 'Audio Player' : 'Video Player');
12337
+ var controlText = createEl('span', {
12338
+ className: 'vjs-control-text',
12339
+ innerHTML: this.localize('{1} is loading.', [playerType])
12340
+ });
12341
+
12342
+ var el = _Component.prototype.createEl.call(this, 'div', {
12343
+ className: 'vjs-loading-spinner',
12344
+ dir: 'ltr'
12345
+ });
12346
+
12347
+ el.appendChild(controlText);
12348
+
12349
+ return el;
12350
+ };
12351
+
12352
+ return LoadingSpinner;
12353
+ }(Component);
12354
+
12355
+ Component.registerComponent('LoadingSpinner', LoadingSpinner);
12356
+
12357
+ /**
12358
+ * @file button.js
12359
+ */
12360
+
12361
+ /**
12362
+ * Base class for all buttons.
12363
+ *
12364
+ * @extends ClickableComponent
12365
+ */
12366
+
12367
+ var Button = function (_ClickableComponent) {
12368
+ inherits(Button, _ClickableComponent);
12369
+
12370
+ function Button() {
12371
+ classCallCheck(this, Button);
12372
+ return possibleConstructorReturn(this, _ClickableComponent.apply(this, arguments));
12373
+ }
12374
+
12375
+ /**
12376
+ * Create the `Button`s DOM element.
12377
+ *
12378
+ * @param {string} [tag="button"]
12379
+ * The element's node type. This argument is IGNORED: no matter what
12380
+ * is passed, it will always create a `button` element.
12381
+ *
12382
+ * @param {Object} [props={}]
12383
+ * An object of properties that should be set on the element.
12384
+ *
12385
+ * @param {Object} [attributes={}]
12386
+ * An object of attributes that should be set on the element.
12387
+ *
12388
+ * @return {Element}
12389
+ * The element that gets created.
12390
+ */
12391
+ Button.prototype.createEl = function createEl(tag) {
12392
+ var props = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
12393
+ var attributes = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
12394
+
12395
+ tag = 'button';
12396
+
12397
+ props = assign({
12398
+ innerHTML: '<span aria-hidden="true" class="vjs-icon-placeholder"></span>',
12399
+ className: this.buildCSSClass()
12400
+ }, props);
12401
+
12402
+ // Add attributes for button element
12403
+ attributes = assign({
12404
+
12405
+ // Necessary since the default button type is "submit"
12406
+ type: 'button'
12407
+ }, attributes);
12408
+
12409
+ var el = Component.prototype.createEl.call(this, tag, props, attributes);
12410
+
12411
+ this.createControlTextEl(el);
12412
+
12413
+ return el;
12414
+ };
12415
+
12416
+ /**
12417
+ * Add a child `Component` inside of this `Button`.
12418
+ *
12419
+ * @param {string|Component} child
12420
+ * The name or instance of a child to add.
12421
+ *
12422
+ * @param {Object} [options={}]
12423
+ * The key/value store of options that will get passed to children of
12424
+ * the child.
12425
+ *
12426
+ * @return {Component}
12427
+ * The `Component` that gets added as a child. When using a string the
12428
+ * `Component` will get created by this process.
12429
+ *
12430
+ * @deprecated since version 5
12431
+ */
12432
+
12433
+
12434
+ Button.prototype.addChild = function addChild(child) {
12435
+ var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
12436
+
12437
+ var className = this.constructor.name;
12438
+
12439
+ log$1.warn('Adding an actionable (user controllable) child to a Button (' + className + ') is not supported; use a ClickableComponent instead.');
12440
+
12441
+ // Avoid the error message generated by ClickableComponent's addChild method
12442
+ return Component.prototype.addChild.call(this, child, options);
12443
+ };
12444
+
12445
+ /**
12446
+ * Enable the `Button` element so that it can be activated or clicked. Use this with
12447
+ * {@link Button#disable}.
12448
+ */
12449
+
12450
+
12451
+ Button.prototype.enable = function enable() {
12452
+ _ClickableComponent.prototype.enable.call(this);
12453
+ this.el_.removeAttribute('disabled');
12454
+ };
12455
+
12456
+ /**
12457
+ * Disable the `Button` element so that it cannot be activated or clicked. Use this with
12458
+ * {@link Button#enable}.
12459
+ */
12460
+
12461
+
12462
+ Button.prototype.disable = function disable() {
12463
+ _ClickableComponent.prototype.disable.call(this);
12464
+ this.el_.setAttribute('disabled', 'disabled');
12465
+ };
12466
+
12467
+ /**
12468
+ * This gets called when a `Button` has focus and `keydown` is triggered via a key
12469
+ * press.
12470
+ *
12471
+ * @param {EventTarget~Event} event
12472
+ * The event that caused this function to get called.
12473
+ *
12474
+ * @listens keydown
12475
+ */
12476
+
12477
+
12478
+ Button.prototype.handleKeyPress = function handleKeyPress(event) {
12479
+
12480
+ // Ignore Space (32) or Enter (13) key operation, which is handled by the browser for a button.
12481
+ if (event.which === 32 || event.which === 13) {
12482
+ return;
12483
+ }
12484
+
12485
+ // Pass keypress handling up for unsupported keys
12486
+ _ClickableComponent.prototype.handleKeyPress.call(this, event);
12487
+ };
12488
+
12489
+ return Button;
12490
+ }(ClickableComponent);
12491
+
12492
+ Component.registerComponent('Button', Button);
12493
+
12494
+ /**
12495
+ * @file big-play-button.js
12496
+ */
12497
+
12498
+ /**
12499
+ * The initial play button that shows before the video has played. The hiding of the
12500
+ * `BigPlayButton` get done via CSS and `Player` states.
12501
+ *
12502
+ * @extends Button
12503
+ */
12504
+
12505
+ var BigPlayButton = function (_Button) {
12506
+ inherits(BigPlayButton, _Button);
12507
+
12508
+ function BigPlayButton(player, options) {
12509
+ classCallCheck(this, BigPlayButton);
12510
+
12511
+ var _this = possibleConstructorReturn(this, _Button.call(this, player, options));
12512
+
12513
+ _this.mouseused_ = false;
12514
+
12515
+ _this.on('mousedown', _this.handleMouseDown);
12516
+ return _this;
12517
+ }
12518
+
12519
+ /**
12520
+ * Builds the default DOM `className`.
12521
+ *
12522
+ * @return {string}
12523
+ * The DOM `className` for this object. Always returns 'vjs-big-play-button'.
12524
+ */
12525
+
12526
+
12527
+ BigPlayButton.prototype.buildCSSClass = function buildCSSClass() {
12528
+ return 'vjs-big-play-button';
12529
+ };
12530
+
12531
+ /**
12532
+ * This gets called when a `BigPlayButton` "clicked". See {@link ClickableComponent}
12533
+ * for more detailed information on what a click can be.
12534
+ *
12535
+ * @param {EventTarget~Event} event
12536
+ * The `keydown`, `tap`, or `click` event that caused this function to be
12537
+ * called.
12538
+ *
12539
+ * @listens tap
12540
+ * @listens click
12541
+ */
12542
+
12543
+
12544
+ BigPlayButton.prototype.handleClick = function handleClick(event) {
12545
+ var playPromise = this.player_.play();
12546
+
12547
+ // exit early if clicked via the mouse
12548
+ if (this.mouseused_ && event.clientX && event.clientY) {
12549
+ silencePromise(playPromise);
12550
+ return;
12551
+ }
12552
+
12553
+ var cb = this.player_.getChild('controlBar');
12554
+ var playToggle = cb && cb.getChild('playToggle');
12555
+
12556
+ if (!playToggle) {
12557
+ this.player_.focus();
12558
+ return;
12559
+ }
12560
+
12561
+ var playFocus = function playFocus() {
12562
+ return playToggle.focus();
12563
+ };
12564
+
12565
+ if (isPromise(playPromise)) {
12566
+ playPromise.then(playFocus, function () {});
12567
+ } else {
12568
+ this.setTimeout(playFocus, 1);
12569
+ }
12570
+ };
12571
+
12572
+ BigPlayButton.prototype.handleKeyPress = function handleKeyPress(event) {
12573
+ this.mouseused_ = false;
12574
+
12575
+ _Button.prototype.handleKeyPress.call(this, event);
12576
+ };
12577
+
12578
+ BigPlayButton.prototype.handleMouseDown = function handleMouseDown(event) {
12579
+ this.mouseused_ = true;
12580
+ };
12581
+
12582
+ return BigPlayButton;
12583
+ }(Button);
12584
+
12585
+ /**
12586
+ * The text that should display over the `BigPlayButton`s controls. Added to for localization.
12587
+ *
12588
+ * @type {string}
12589
+ * @private
12590
+ */
12591
+
12592
+
12593
+ BigPlayButton.prototype.controlText_ = 'Play Video';
12594
+
12595
+ Component.registerComponent('BigPlayButton', BigPlayButton);
12596
+
12597
+ /**
12598
+ * @file close-button.js
12599
+ */
12600
+
12601
+ /**
12602
+ * The `CloseButton` is a `{@link Button}` that fires a `close` event when
12603
+ * it gets clicked.
12604
+ *
12605
+ * @extends Button
12606
+ */
12607
+
12608
+ var CloseButton = function (_Button) {
12609
+ inherits(CloseButton, _Button);
12610
+
12611
+ /**
12612
+ * Creates an instance of the this class.
12613
+ *
12614
+ * @param {Player} player
12615
+ * The `Player` that this class should be attached to.
12616
+ *
12617
+ * @param {Object} [options]
12618
+ * The key/value store of player options.
12619
+ */
12620
+ function CloseButton(player, options) {
12621
+ classCallCheck(this, CloseButton);
12622
+
12623
+ var _this = possibleConstructorReturn(this, _Button.call(this, player, options));
12624
+
12625
+ _this.controlText(options && options.controlText || _this.localize('Close'));
12626
+ return _this;
12627
+ }
12628
+
12629
+ /**
12630
+ * Builds the default DOM `className`.
12631
+ *
12632
+ * @return {string}
12633
+ * The DOM `className` for this object.
12634
+ */
12635
+
12636
+
12637
+ CloseButton.prototype.buildCSSClass = function buildCSSClass() {
12638
+ return 'vjs-close-button ' + _Button.prototype.buildCSSClass.call(this);
12639
+ };
12640
+
12641
+ /**
12642
+ * This gets called when a `CloseButton` gets clicked. See
12643
+ * {@link ClickableComponent#handleClick} for more information on when this will be
12644
+ * triggered
12645
+ *
12646
+ * @param {EventTarget~Event} event
12647
+ * The `keydown`, `tap`, or `click` event that caused this function to be
12648
+ * called.
12649
+ *
12650
+ * @listens tap
12651
+ * @listens click
12652
+ * @fires CloseButton#close
12653
+ */
12654
+
12655
+
12656
+ CloseButton.prototype.handleClick = function handleClick(event) {
12657
+
12658
+ /**
12659
+ * Triggered when the a `CloseButton` is clicked.
12660
+ *
12661
+ * @event CloseButton#close
12662
+ * @type {EventTarget~Event}
12663
+ *
12664
+ * @property {boolean} [bubbles=false]
12665
+ * set to false so that the close event does not
12666
+ * bubble up to parents if there is no listener
12667
+ */
12668
+ this.trigger({ type: 'close', bubbles: false });
12669
+ };
12670
+
12671
+ return CloseButton;
12672
+ }(Button);
12673
+
12674
+ Component.registerComponent('CloseButton', CloseButton);
12675
+
12676
+ /**
12677
+ * @file play-toggle.js
12678
+ */
12679
+
12680
+ /**
12681
+ * Button to toggle between play and pause.
12682
+ *
12683
+ * @extends Button
12684
+ */
12685
+
12686
+ var PlayToggle = function (_Button) {
12687
+ inherits(PlayToggle, _Button);
12688
+
12689
+ /**
12690
+ * Creates an instance of this class.
12691
+ *
12692
+ * @param {Player} player
12693
+ * The `Player` that this class should be attached to.
12694
+ *
12695
+ * @param {Object} [options]
12696
+ * The key/value store of player options.
12697
+ */
12698
+ function PlayToggle(player, options) {
12699
+ classCallCheck(this, PlayToggle);
12700
+
12701
+ var _this = possibleConstructorReturn(this, _Button.call(this, player, options));
12702
+
12703
+ _this.on(player, 'play', _this.handlePlay);
12704
+ _this.on(player, 'pause', _this.handlePause);
12705
+ _this.on(player, 'ended', _this.handleEnded);
12706
+ return _this;
12707
+ }
12708
+
12709
+ /**
12710
+ * Builds the default DOM `className`.
12711
+ *
12712
+ * @return {string}
12713
+ * The DOM `className` for this object.
12714
+ */
12715
+
12716
+
12717
+ PlayToggle.prototype.buildCSSClass = function buildCSSClass() {
12718
+ return 'vjs-play-control ' + _Button.prototype.buildCSSClass.call(this);
12719
+ };
12720
+
12721
+ /**
12722
+ * This gets called when an `PlayToggle` is "clicked". See
12723
+ * {@link ClickableComponent} for more detailed information on what a click can be.
12724
+ *
12725
+ * @param {EventTarget~Event} [event]
12726
+ * The `keydown`, `tap`, or `click` event that caused this function to be
12727
+ * called.
12728
+ *
12729
+ * @listens tap
12730
+ * @listens click
12731
+ */
12732
+
12733
+
12734
+ PlayToggle.prototype.handleClick = function handleClick(event) {
12735
+ if (this.player_.paused()) {
12736
+ this.player_.play();
12737
+ } else {
12738
+ this.player_.pause();
12739
+ }
12740
+ };
12741
+
12742
+ /**
12743
+ * This gets called once after the video has ended and the user seeks so that
12744
+ * we can change the replay button back to a play button.
12745
+ *
12746
+ * @param {EventTarget~Event} [event]
12747
+ * The event that caused this function to run.
12748
+ *
12749
+ * @listens Player#seeked
12750
+ */
12751
+
12752
+
12753
+ PlayToggle.prototype.handleSeeked = function handleSeeked(event) {
12754
+ this.removeClass('vjs-ended');
12755
+
12756
+ if (this.player_.paused()) {
12757
+ this.handlePause(event);
12758
+ } else {
12759
+ this.handlePlay(event);
12760
+ }
12761
+ };
12762
+
12763
+ /**
12764
+ * Add the vjs-playing class to the element so it can change appearance.
12765
+ *
12766
+ * @param {EventTarget~Event} [event]
12767
+ * The event that caused this function to run.
12768
+ *
12769
+ * @listens Player#play
12770
+ */
12771
+
12772
+
12773
+ PlayToggle.prototype.handlePlay = function handlePlay(event) {
12774
+ this.removeClass('vjs-ended');
12775
+ this.removeClass('vjs-paused');
12776
+ this.addClass('vjs-playing');
12777
+ // change the button text to "Pause"
12778
+ this.controlText('Pause');
12779
+ };
12780
+
12781
+ /**
12782
+ * Add the vjs-paused class to the element so it can change appearance.
12783
+ *
12784
+ * @param {EventTarget~Event} [event]
12785
+ * The event that caused this function to run.
12786
+ *
12787
+ * @listens Player#pause
12788
+ */
12789
+
12790
+
12791
+ PlayToggle.prototype.handlePause = function handlePause(event) {
12792
+ this.removeClass('vjs-playing');
12793
+ this.addClass('vjs-paused');
12794
+ // change the button text to "Play"
12795
+ this.controlText('Play');
12796
+ };
12797
+
12798
+ /**
12799
+ * Add the vjs-ended class to the element so it can change appearance
12800
+ *
12801
+ * @param {EventTarget~Event} [event]
12802
+ * The event that caused this function to run.
12803
+ *
12804
+ * @listens Player#ended
12805
+ */
12806
+
12807
+
12808
+ PlayToggle.prototype.handleEnded = function handleEnded(event) {
12809
+ this.removeClass('vjs-playing');
12810
+ this.addClass('vjs-ended');
12811
+ // change the button text to "Replay"
12812
+ this.controlText('Replay');
12813
+
12814
+ // on the next seek remove the replay button
12815
+ this.one(this.player_, 'seeked', this.handleSeeked);
12816
+ };
12817
+
12818
+ return PlayToggle;
12819
+ }(Button);
12820
+
12821
+ /**
12822
+ * The text that should display over the `PlayToggle`s controls. Added for localization.
12823
+ *
12824
+ * @type {string}
12825
+ * @private
12826
+ */
12827
+
12828
+
12829
+ PlayToggle.prototype.controlText_ = 'Play';
12830
+
12831
+ Component.registerComponent('PlayToggle', PlayToggle);
12832
+
12833
+ /**
12834
+ * @file format-time.js
12835
+ * @module format-time
12836
+ */
12837
+
12838
+ /**
12839
+ * Format seconds as a time string, H:MM:SS or M:SS. Supplying a guide (in seconds)
12840
+ * will force a number of leading zeros to cover the length of the guide.
12841
+ *
12842
+ * @param {number} seconds
12843
+ * Number of seconds to be turned into a string
12844
+ *
12845
+ * @param {number} guide
12846
+ * Number (in seconds) to model the string after
12847
+ *
12848
+ * @return {string}
12849
+ * Time formatted as H:MM:SS or M:SS
12850
+ */
12851
+ var defaultImplementation = function defaultImplementation(seconds, guide) {
12852
+ seconds = seconds < 0 ? 0 : seconds;
12853
+ var s = Math.floor(seconds % 60);
12854
+ var m = Math.floor(seconds / 60 % 60);
12855
+ var h = Math.floor(seconds / 3600);
12856
+ var gm = Math.floor(guide / 60 % 60);
12857
+ var gh = Math.floor(guide / 3600);
12858
+
12859
+ // handle invalid times
12860
+ if (isNaN(seconds) || seconds === Infinity) {
12861
+ // '-' is false for all relational operators (e.g. <, >=) so this setting
12862
+ // will add the minimum number of fields specified by the guide
12863
+ h = m = s = '-';
12864
+ }
12865
+
12866
+ // Check if we need to show hours
12867
+ h = h > 0 || gh > 0 ? h + ':' : '';
12868
+
12869
+ // If hours are showing, we may need to add a leading zero.
12870
+ // Always show at least one digit of minutes.
12871
+ m = ((h || gm >= 10) && m < 10 ? '0' + m : m) + ':';
12872
+
12873
+ // Check if leading zero is need for seconds
12874
+ s = s < 10 ? '0' + s : s;
12875
+
12876
+ return h + m + s;
12877
+ };
12878
+
12879
+ var implementation = defaultImplementation;
12880
+
12881
+ /**
12882
+ * Replaces the default formatTime implementation with a custom implementation.
12883
+ *
12884
+ * @param {Function} customImplementation
12885
+ * A function which will be used in place of the default formatTime implementation.
12886
+ * Will receive the current time in seconds and the guide (in seconds) as arguments.
12887
+ */
12888
+ function setFormatTime(customImplementation) {
12889
+ implementation = customImplementation;
12890
+ }
12891
+
12892
+ /**
12893
+ * Resets formatTime to the default implementation.
12894
+ */
12895
+ function resetFormatTime() {
12896
+ implementation = defaultImplementation;
12897
+ }
12898
+
12899
+ function formatTime (seconds) {
12900
+ var guide = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : seconds;
12901
+
12902
+ return implementation(seconds, guide);
12903
+ }
12904
+
12905
+ /**
12906
+ * @file time-display.js
12907
+ */
12908
+
12909
+ /**
12910
+ * Displays the time left in the video
12911
+ *
12912
+ * @extends Component
12913
+ */
12914
+
12915
+ var TimeDisplay = function (_Component) {
12916
+ inherits(TimeDisplay, _Component);
12917
+
12918
+ /**
12919
+ * Creates an instance of this class.
12920
+ *
12921
+ * @param {Player} player
12922
+ * The `Player` that this class should be attached to.
12923
+ *
12924
+ * @param {Object} [options]
12925
+ * The key/value store of player options.
12926
+ */
12927
+ function TimeDisplay(player, options) {
12928
+ classCallCheck(this, TimeDisplay);
12929
+
12930
+ var _this = possibleConstructorReturn(this, _Component.call(this, player, options));
12931
+
12932
+ _this.throttledUpdateContent = throttle(bind(_this, _this.updateContent), 25);
12933
+ _this.on(player, 'timeupdate', _this.throttledUpdateContent);
12934
+ return _this;
12935
+ }
12936
+
12937
+ /**
12938
+ * Create the `Component`'s DOM element
12939
+ *
12940
+ * @return {Element}
12941
+ * The element that was created.
12942
+ */
12943
+
12944
+
12945
+ TimeDisplay.prototype.createEl = function createEl$$1(plainName) {
12946
+ var className = this.buildCSSClass();
12947
+ var el = _Component.prototype.createEl.call(this, 'div', {
12948
+ className: className + ' vjs-time-control vjs-control',
12949
+ innerHTML: '<span class="vjs-control-text">' + this.localize(this.labelText_) + '\xA0</span>'
12950
+ });
12951
+
12952
+ this.contentEl_ = createEl('span', {
12953
+ className: className + '-display'
12954
+ }, {
12955
+ // tell screen readers not to automatically read the time as it changes
12956
+ 'aria-live': 'off'
12957
+ });
12958
+
12959
+ this.updateTextNode_();
12960
+ el.appendChild(this.contentEl_);
12961
+ return el;
12962
+ };
12963
+
12964
+ TimeDisplay.prototype.dispose = function dispose() {
12965
+ this.contentEl_ = null;
12966
+ this.textNode_ = null;
12967
+
12968
+ _Component.prototype.dispose.call(this);
12969
+ };
12970
+
12971
+ /**
12972
+ * Updates the "remaining time" text node with new content using the
12973
+ * contents of the `formattedTime_` property.
12974
+ *
12975
+ * @private
12976
+ */
12977
+
12978
+
12979
+ TimeDisplay.prototype.updateTextNode_ = function updateTextNode_() {
12980
+ if (!this.contentEl_) {
12981
+ return;
12982
+ }
12983
+
12984
+ while (this.contentEl_.firstChild) {
12985
+ this.contentEl_.removeChild(this.contentEl_.firstChild);
12986
+ }
12987
+
12988
+ this.textNode_ = document_1.createTextNode(this.formattedTime_ || this.formatTime_(0));
12989
+ this.contentEl_.appendChild(this.textNode_);
12990
+ };
12991
+
12992
+ /**
12993
+ * Generates a formatted time for this component to use in display.
12994
+ *
12995
+ * @param {number} time
12996
+ * A numeric time, in seconds.
12997
+ *
12998
+ * @return {string}
12999
+ * A formatted time
13000
+ *
13001
+ * @private
13002
+ */
13003
+
13004
+
13005
+ TimeDisplay.prototype.formatTime_ = function formatTime_(time) {
13006
+ return formatTime(time);
13007
+ };
13008
+
13009
+ /**
13010
+ * Updates the time display text node if it has what was passed in changed
13011
+ * the formatted time.
13012
+ *
13013
+ * @param {number} time
13014
+ * The time to update to
13015
+ *
13016
+ * @private
13017
+ */
13018
+
13019
+
13020
+ TimeDisplay.prototype.updateFormattedTime_ = function updateFormattedTime_(time) {
13021
+ var formattedTime = this.formatTime_(time);
13022
+
13023
+ if (formattedTime === this.formattedTime_) {
13024
+ return;
13025
+ }
13026
+
13027
+ this.formattedTime_ = formattedTime;
13028
+ this.requestAnimationFrame(this.updateTextNode_);
13029
+ };
13030
+
13031
+ /**
13032
+ * To be filled out in the child class, should update the displayed time
13033
+ * in accordance with the fact that the current time has changed.
13034
+ *
13035
+ * @param {EventTarget~Event} [event]
13036
+ * The `timeupdate` event that caused this to run.
13037
+ *
13038
+ * @listens Player#timeupdate
13039
+ */
13040
+
13041
+
13042
+ TimeDisplay.prototype.updateContent = function updateContent(event) {};
13043
+
13044
+ return TimeDisplay;
13045
+ }(Component);
13046
+
13047
+ /**
13048
+ * The text that is added to the `TimeDisplay` for screen reader users.
13049
+ *
13050
+ * @type {string}
13051
+ * @private
13052
+ */
13053
+
13054
+
13055
+ TimeDisplay.prototype.labelText_ = 'Time';
13056
+
13057
+ /**
13058
+ * The text that should display over the `TimeDisplay`s controls. Added to for localization.
13059
+ *
13060
+ * @type {string}
13061
+ * @private
13062
+ *
13063
+ * @deprecated in v7; controlText_ is not used in non-active display Components
13064
+ */
13065
+ TimeDisplay.prototype.controlText_ = 'Time';
13066
+
13067
+ Component.registerComponent('TimeDisplay', TimeDisplay);
13068
+
13069
+ /**
13070
+ * @file current-time-display.js
13071
+ */
13072
+
13073
+ /**
13074
+ * Displays the current time
13075
+ *
13076
+ * @extends Component
13077
+ */
13078
+
13079
+ var CurrentTimeDisplay = function (_TimeDisplay) {
13080
+ inherits(CurrentTimeDisplay, _TimeDisplay);
13081
+
13082
+ /**
13083
+ * Creates an instance of this class.
13084
+ *
13085
+ * @param {Player} player
13086
+ * The `Player` that this class should be attached to.
13087
+ *
13088
+ * @param {Object} [options]
13089
+ * The key/value store of player options.
13090
+ */
13091
+ function CurrentTimeDisplay(player, options) {
13092
+ classCallCheck(this, CurrentTimeDisplay);
13093
+
13094
+ var _this = possibleConstructorReturn(this, _TimeDisplay.call(this, player, options));
13095
+
13096
+ _this.on(player, 'ended', _this.handleEnded);
13097
+ return _this;
13098
+ }
13099
+
13100
+ /**
13101
+ * Builds the default DOM `className`.
13102
+ *
13103
+ * @return {string}
13104
+ * The DOM `className` for this object.
13105
+ */
13106
+
13107
+
13108
+ CurrentTimeDisplay.prototype.buildCSSClass = function buildCSSClass() {
13109
+ return 'vjs-current-time';
13110
+ };
13111
+
13112
+ /**
13113
+ * Update current time display
13114
+ *
13115
+ * @param {EventTarget~Event} [event]
13116
+ * The `timeupdate` event that caused this function to run.
13117
+ *
13118
+ * @listens Player#timeupdate
13119
+ */
13120
+
13121
+
13122
+ CurrentTimeDisplay.prototype.updateContent = function updateContent(event) {
13123
+ // Allows for smooth scrubbing, when player can't keep up.
13124
+ var time = this.player_.scrubbing() ? this.player_.getCache().currentTime : this.player_.currentTime();
13125
+
13126
+ this.updateFormattedTime_(time);
13127
+ };
13128
+
13129
+ /**
13130
+ * When the player fires ended there should be no time left. Sadly
13131
+ * this is not always the case, lets make it seem like that is the case
13132
+ * for users.
13133
+ *
13134
+ * @param {EventTarget~Event} [event]
13135
+ * The `ended` event that caused this to run.
13136
+ *
13137
+ * @listens Player#ended
13138
+ */
13139
+
13140
+
13141
+ CurrentTimeDisplay.prototype.handleEnded = function handleEnded(event) {
13142
+ if (!this.player_.duration()) {
13143
+ return;
13144
+ }
13145
+ this.updateFormattedTime_(this.player_.duration());
13146
+ };
13147
+
13148
+ return CurrentTimeDisplay;
13149
+ }(TimeDisplay);
13150
+
13151
+ /**
13152
+ * The text that is added to the `CurrentTimeDisplay` for screen reader users.
13153
+ *
13154
+ * @type {string}
13155
+ * @private
13156
+ */
13157
+
13158
+
13159
+ CurrentTimeDisplay.prototype.labelText_ = 'Current Time';
13160
+
13161
+ /**
13162
+ * The text that should display over the `CurrentTimeDisplay`s controls. Added to for localization.
13163
+ *
13164
+ * @type {string}
13165
+ * @private
13166
+ *
13167
+ * @deprecated in v7; controlText_ is not used in non-active display Components
13168
+ */
13169
+ CurrentTimeDisplay.prototype.controlText_ = 'Current Time';
13170
+
13171
+ Component.registerComponent('CurrentTimeDisplay', CurrentTimeDisplay);
13172
+
13173
+ /**
13174
+ * @file duration-display.js
13175
+ */
13176
+
13177
+ /**
13178
+ * Displays the duration
13179
+ *
13180
+ * @extends Component
13181
+ */
13182
+
13183
+ var DurationDisplay = function (_TimeDisplay) {
13184
+ inherits(DurationDisplay, _TimeDisplay);
13185
+
13186
+ /**
13187
+ * Creates an instance of this class.
13188
+ *
13189
+ * @param {Player} player
13190
+ * The `Player` that this class should be attached to.
13191
+ *
13192
+ * @param {Object} [options]
13193
+ * The key/value store of player options.
13194
+ */
13195
+ function DurationDisplay(player, options) {
13196
+ classCallCheck(this, DurationDisplay);
13197
+
13198
+ // we do not want to/need to throttle duration changes,
13199
+ // as they should always display the changed duration as
13200
+ // it has changed
13201
+ var _this = possibleConstructorReturn(this, _TimeDisplay.call(this, player, options));
13202
+
13203
+ _this.on(player, 'durationchange', _this.updateContent);
13204
+
13205
+ // Also listen for timeupdate (in the parent) and loadedmetadata because removing those
13206
+ // listeners could have broken dependent applications/libraries. These
13207
+ // can likely be removed for 7.0.
13208
+ _this.on(player, 'loadedmetadata', _this.throttledUpdateContent);
13209
+ return _this;
13210
+ }
13211
+
13212
+ /**
13213
+ * Builds the default DOM `className`.
13214
+ *
13215
+ * @return {string}
13216
+ * The DOM `className` for this object.
13217
+ */
13218
+
13219
+
13220
+ DurationDisplay.prototype.buildCSSClass = function buildCSSClass() {
13221
+ return 'vjs-duration';
13222
+ };
13223
+
13224
+ /**
13225
+ * Update duration time display.
13226
+ *
13227
+ * @param {EventTarget~Event} [event]
13228
+ * The `durationchange`, `timeupdate`, or `loadedmetadata` event that caused
13229
+ * this function to be called.
13230
+ *
13231
+ * @listens Player#durationchange
13232
+ * @listens Player#timeupdate
13233
+ * @listens Player#loadedmetadata
13234
+ */
13235
+
13236
+
13237
+ DurationDisplay.prototype.updateContent = function updateContent(event) {
13238
+ var duration = this.player_.duration();
13239
+
13240
+ if (duration && this.duration_ !== duration) {
13241
+ this.duration_ = duration;
13242
+ this.updateFormattedTime_(duration);
13243
+ }
13244
+ };
13245
+
13246
+ return DurationDisplay;
13247
+ }(TimeDisplay);
13248
+
13249
+ /**
13250
+ * The text that is added to the `DurationDisplay` for screen reader users.
13251
+ *
13252
+ * @type {string}
13253
+ * @private
13254
+ */
13255
+
13256
+
13257
+ DurationDisplay.prototype.labelText_ = 'Duration';
13258
+
13259
+ /**
13260
+ * The text that should display over the `DurationDisplay`s controls. Added to for localization.
13261
+ *
13262
+ * @type {string}
13263
+ * @private
13264
+ *
13265
+ * @deprecated in v7; controlText_ is not used in non-active display Components
13266
+ */
13267
+ DurationDisplay.prototype.controlText_ = 'Duration';
13268
+
13269
+ Component.registerComponent('DurationDisplay', DurationDisplay);
13270
+
13271
+ /**
13272
+ * @file time-divider.js
13273
+ */
13274
+
13275
+ /**
13276
+ * The separator between the current time and duration.
13277
+ * Can be hidden if it's not needed in the design.
13278
+ *
13279
+ * @extends Component
13280
+ */
13281
+
13282
+ var TimeDivider = function (_Component) {
13283
+ inherits(TimeDivider, _Component);
13284
+
13285
+ function TimeDivider() {
13286
+ classCallCheck(this, TimeDivider);
13287
+ return possibleConstructorReturn(this, _Component.apply(this, arguments));
13288
+ }
13289
+
13290
+ /**
13291
+ * Create the component's DOM element
13292
+ *
13293
+ * @return {Element}
13294
+ * The element that was created.
13295
+ */
13296
+ TimeDivider.prototype.createEl = function createEl() {
13297
+ return _Component.prototype.createEl.call(this, 'div', {
13298
+ className: 'vjs-time-control vjs-time-divider',
13299
+ innerHTML: '<div><span>/</span></div>'
13300
+ });
13301
+ };
13302
+
13303
+ return TimeDivider;
13304
+ }(Component);
13305
+
13306
+ Component.registerComponent('TimeDivider', TimeDivider);
13307
+
13308
+ /**
13309
+ * @file remaining-time-display.js
13310
+ */
13311
+ /**
13312
+ * Displays the time left in the video
13313
+ *
13314
+ * @extends Component
13315
+ */
13316
+
13317
+ var RemainingTimeDisplay = function (_TimeDisplay) {
13318
+ inherits(RemainingTimeDisplay, _TimeDisplay);
13319
+
13320
+ /**
13321
+ * Creates an instance of this class.
13322
+ *
13323
+ * @param {Player} player
13324
+ * The `Player` that this class should be attached to.
13325
+ *
13326
+ * @param {Object} [options]
13327
+ * The key/value store of player options.
13328
+ */
13329
+ function RemainingTimeDisplay(player, options) {
13330
+ classCallCheck(this, RemainingTimeDisplay);
13331
+
13332
+ var _this = possibleConstructorReturn(this, _TimeDisplay.call(this, player, options));
13333
+
13334
+ _this.on(player, 'durationchange', _this.throttledUpdateContent);
13335
+ _this.on(player, 'ended', _this.handleEnded);
13336
+ return _this;
13337
+ }
13338
+
13339
+ /**
13340
+ * Builds the default DOM `className`.
13341
+ *
13342
+ * @return {string}
13343
+ * The DOM `className` for this object.
13344
+ */
13345
+
13346
+
13347
+ RemainingTimeDisplay.prototype.buildCSSClass = function buildCSSClass() {
13348
+ return 'vjs-remaining-time';
13349
+ };
13350
+
13351
+ /**
13352
+ * The remaining time display prefixes numbers with a "minus" character.
13353
+ *
13354
+ * @param {number} time
13355
+ * A numeric time, in seconds.
13356
+ *
13357
+ * @return {string}
13358
+ * A formatted time
13359
+ *
13360
+ * @private
13361
+ */
13362
+
13363
+
13364
+ RemainingTimeDisplay.prototype.formatTime_ = function formatTime_(time) {
13365
+ // TODO: The "-" should be decorative, and not announced by a screen reader
13366
+ return '-' + _TimeDisplay.prototype.formatTime_.call(this, time);
13367
+ };
13368
+
13369
+ /**
13370
+ * Update remaining time display.
13371
+ *
13372
+ * @param {EventTarget~Event} [event]
13373
+ * The `timeupdate` or `durationchange` event that caused this to run.
13374
+ *
13375
+ * @listens Player#timeupdate
13376
+ * @listens Player#durationchange
13377
+ */
13378
+
13379
+
13380
+ RemainingTimeDisplay.prototype.updateContent = function updateContent(event) {
13381
+ if (!this.player_.duration()) {
13382
+ return;
13383
+ }
13384
+
13385
+ // @deprecated We should only use remainingTimeDisplay
13386
+ // as of video.js 7
13387
+ if (this.player_.remainingTimeDisplay) {
13388
+ this.updateFormattedTime_(this.player_.remainingTimeDisplay());
13389
+ } else {
13390
+ this.updateFormattedTime_(this.player_.remainingTime());
13391
+ }
13392
+ };
13393
+
13394
+ /**
13395
+ * When the player fires ended there should be no time left. Sadly
13396
+ * this is not always the case, lets make it seem like that is the case
13397
+ * for users.
13398
+ *
13399
+ * @param {EventTarget~Event} [event]
13400
+ * The `ended` event that caused this to run.
13401
+ *
13402
+ * @listens Player#ended
13403
+ */
13404
+
13405
+
13406
+ RemainingTimeDisplay.prototype.handleEnded = function handleEnded(event) {
13407
+ if (!this.player_.duration()) {
13408
+ return;
13409
+ }
13410
+ this.updateFormattedTime_(0);
13411
+ };
13412
+
13413
+ return RemainingTimeDisplay;
13414
+ }(TimeDisplay);
13415
+
13416
+ /**
13417
+ * The text that is added to the `RemainingTimeDisplay` for screen reader users.
13418
+ *
13419
+ * @type {string}
13420
+ * @private
13421
+ */
13422
+
13423
+
13424
+ RemainingTimeDisplay.prototype.labelText_ = 'Remaining Time';
13425
+
13426
+ /**
13427
+ * The text that should display over the `RemainingTimeDisplay`s controls. Added to for localization.
13428
+ *
13429
+ * @type {string}
13430
+ * @private
13431
+ *
13432
+ * @deprecated in v7; controlText_ is not used in non-active display Components
13433
+ */
13434
+ RemainingTimeDisplay.prototype.controlText_ = 'Remaining Time';
13435
+
13436
+ Component.registerComponent('RemainingTimeDisplay', RemainingTimeDisplay);
13437
+
13438
+ /**
13439
+ * @file live-display.js
13440
+ */
13441
+
13442
+ // TODO - Future make it click to snap to live
13443
+
13444
+ /**
13445
+ * Displays the live indicator when duration is Infinity.
13446
+ *
13447
+ * @extends Component
13448
+ */
13449
+
13450
+ var LiveDisplay = function (_Component) {
13451
+ inherits(LiveDisplay, _Component);
13452
+
13453
+ /**
13454
+ * Creates an instance of this class.
13455
+ *
13456
+ * @param {Player} player
13457
+ * The `Player` that this class should be attached to.
13458
+ *
13459
+ * @param {Object} [options]
13460
+ * The key/value store of player options.
13461
+ */
13462
+ function LiveDisplay(player, options) {
13463
+ classCallCheck(this, LiveDisplay);
13464
+
13465
+ var _this = possibleConstructorReturn(this, _Component.call(this, player, options));
13466
+
13467
+ _this.updateShowing();
13468
+ _this.on(_this.player(), 'durationchange', _this.updateShowing);
13469
+ return _this;
13470
+ }
13471
+
13472
+ /**
13473
+ * Create the `Component`'s DOM element
13474
+ *
13475
+ * @return {Element}
13476
+ * The element that was created.
13477
+ */
13478
+
13479
+
13480
+ LiveDisplay.prototype.createEl = function createEl$$1() {
13481
+ var el = _Component.prototype.createEl.call(this, 'div', {
13482
+ className: 'vjs-live-control vjs-control'
13483
+ });
13484
+
13485
+ this.contentEl_ = createEl('div', {
13486
+ className: 'vjs-live-display',
13487
+ innerHTML: '<span class="vjs-control-text">' + this.localize('Stream Type') + '\xA0</span>' + this.localize('LIVE')
13488
+ }, {
13489
+ 'aria-live': 'off'
13490
+ });
13491
+
13492
+ el.appendChild(this.contentEl_);
13493
+ return el;
13494
+ };
13495
+
13496
+ LiveDisplay.prototype.dispose = function dispose() {
13497
+ this.contentEl_ = null;
13498
+
13499
+ _Component.prototype.dispose.call(this);
13500
+ };
13501
+
13502
+ /**
13503
+ * Check the duration to see if the LiveDisplay should be showing or not. Then show/hide
13504
+ * it accordingly
13505
+ *
13506
+ * @param {EventTarget~Event} [event]
13507
+ * The {@link Player#durationchange} event that caused this function to run.
13508
+ *
13509
+ * @listens Player#durationchange
13510
+ */
13511
+
13512
+
13513
+ LiveDisplay.prototype.updateShowing = function updateShowing(event) {
13514
+ if (this.player().duration() === Infinity) {
13515
+ this.show();
13516
+ } else {
13517
+ this.hide();
13518
+ }
13519
+ };
13520
+
13521
+ return LiveDisplay;
13522
+ }(Component);
13523
+
13524
+ Component.registerComponent('LiveDisplay', LiveDisplay);
13525
+
13526
+ /**
13527
+ * @file slider.js
13528
+ */
13529
+
13530
+ /**
13531
+ * The base functionality for a slider. Can be vertical or horizontal.
13532
+ * For instance the volume bar or the seek bar on a video is a slider.
13533
+ *
13534
+ * @extends Component
13535
+ */
13536
+
13537
+ var Slider = function (_Component) {
13538
+ inherits(Slider, _Component);
13539
+
13540
+ /**
13541
+ * Create an instance of this class
13542
+ *
13543
+ * @param {Player} player
13544
+ * The `Player` that this class should be attached to.
13545
+ *
13546
+ * @param {Object} [options]
13547
+ * The key/value store of player options.
13548
+ */
13549
+ function Slider(player, options) {
13550
+ classCallCheck(this, Slider);
13551
+
13552
+ // Set property names to bar to match with the child Slider class is looking for
13553
+ var _this = possibleConstructorReturn(this, _Component.call(this, player, options));
13554
+
13555
+ _this.bar = _this.getChild(_this.options_.barName);
13556
+
13557
+ // Set a horizontal or vertical class on the slider depending on the slider type
13558
+ _this.vertical(!!_this.options_.vertical);
13559
+
13560
+ _this.enable();
13561
+ return _this;
13562
+ }
13563
+
13564
+ /**
13565
+ * Are controls are currently enabled for this slider or not.
13566
+ *
13567
+ * @return {boolean}
13568
+ * true if controls are enabled, false otherwise
13569
+ */
13570
+
13571
+
13572
+ Slider.prototype.enabled = function enabled() {
13573
+ return this.enabled_;
13574
+ };
13575
+
13576
+ /**
13577
+ * Enable controls for this slider if they are disabled
13578
+ */
13579
+
13580
+
13581
+ Slider.prototype.enable = function enable() {
13582
+ if (this.enabled()) {
13583
+ return;
13584
+ }
13585
+
13586
+ this.on('mousedown', this.handleMouseDown);
13587
+ this.on('touchstart', this.handleMouseDown);
13588
+ this.on('focus', this.handleFocus);
13589
+ this.on('blur', this.handleBlur);
13590
+ this.on('click', this.handleClick);
13591
+
13592
+ this.on(this.player_, 'controlsvisible', this.update);
13593
+
13594
+ if (this.playerEvent) {
13595
+ this.on(this.player_, this.playerEvent, this.update);
13596
+ }
13597
+
13598
+ this.removeClass('disabled');
13599
+ this.setAttribute('tabindex', 0);
13600
+
13601
+ this.enabled_ = true;
13602
+ };
13603
+
13604
+ /**
13605
+ * Disable controls for this slider if they are enabled
13606
+ */
13607
+
13608
+
13609
+ Slider.prototype.disable = function disable() {
13610
+ if (!this.enabled()) {
13611
+ return;
13612
+ }
13613
+ var doc = this.bar.el_.ownerDocument;
13614
+
13615
+ this.off('mousedown', this.handleMouseDown);
13616
+ this.off('touchstart', this.handleMouseDown);
13617
+ this.off('focus', this.handleFocus);
13618
+ this.off('blur', this.handleBlur);
13619
+ this.off('click', this.handleClick);
13620
+ this.off(this.player_, 'controlsvisible', this.update);
13621
+ this.off(doc, 'mousemove', this.handleMouseMove);
13622
+ this.off(doc, 'mouseup', this.handleMouseUp);
13623
+ this.off(doc, 'touchmove', this.handleMouseMove);
13624
+ this.off(doc, 'touchend', this.handleMouseUp);
13625
+ this.removeAttribute('tabindex');
13626
+
13627
+ this.addClass('disabled');
13628
+
13629
+ if (this.playerEvent) {
13630
+ this.off(this.player_, this.playerEvent, this.update);
13631
+ }
13632
+ this.enabled_ = false;
13633
+ };
13634
+
13635
+ /**
13636
+ * Create the `Slider`s DOM element.
13637
+ *
13638
+ * @param {string} type
13639
+ * Type of element to create.
13640
+ *
13641
+ * @param {Object} [props={}]
13642
+ * List of properties in Object form.
13643
+ *
13644
+ * @param {Object} [attributes={}]
13645
+ * list of attributes in Object form.
13646
+ *
13647
+ * @return {Element}
13648
+ * The element that gets created.
13649
+ */
13650
+
13651
+
13652
+ Slider.prototype.createEl = function createEl$$1(type) {
13653
+ var props = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
13654
+ var attributes = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
13655
+
13656
+ // Add the slider element class to all sub classes
13657
+ props.className = props.className + ' vjs-slider';
13658
+ props = assign({
13659
+ tabIndex: 0
13660
+ }, props);
13661
+
13662
+ attributes = assign({
13663
+ 'role': 'slider',
13664
+ 'aria-valuenow': 0,
13665
+ 'aria-valuemin': 0,
13666
+ 'aria-valuemax': 100,
13667
+ 'tabIndex': 0
13668
+ }, attributes);
13669
+
13670
+ return _Component.prototype.createEl.call(this, type, props, attributes);
13671
+ };
13672
+
13673
+ /**
13674
+ * Handle `mousedown` or `touchstart` events on the `Slider`.
13675
+ *
13676
+ * @param {EventTarget~Event} event
13677
+ * `mousedown` or `touchstart` event that triggered this function
13678
+ *
13679
+ * @listens mousedown
13680
+ * @listens touchstart
13681
+ * @fires Slider#slideractive
13682
+ */
13683
+
13684
+
13685
+ Slider.prototype.handleMouseDown = function handleMouseDown(event) {
13686
+ var doc = this.bar.el_.ownerDocument;
13687
+
13688
+ if (event.type === 'mousedown') {
13689
+ event.preventDefault();
13690
+ }
13691
+ // Do not call preventDefault() on touchstart in Chrome
13692
+ // to avoid console warnings. Use a 'touch-action: none' style
13693
+ // instead to prevent unintented scrolling.
13694
+ // https://developers.google.com/web/updates/2017/01/scrolling-intervention
13695
+ if (event.type === 'touchstart' && !IS_CHROME) {
13696
+ event.preventDefault();
13697
+ }
13698
+ blockTextSelection();
13699
+
13700
+ this.addClass('vjs-sliding');
13701
+ /**
13702
+ * Triggered when the slider is in an active state
13703
+ *
13704
+ * @event Slider#slideractive
13705
+ * @type {EventTarget~Event}
13706
+ */
13707
+ this.trigger('slideractive');
13708
+
13709
+ this.on(doc, 'mousemove', this.handleMouseMove);
13710
+ this.on(doc, 'mouseup', this.handleMouseUp);
13711
+ this.on(doc, 'touchmove', this.handleMouseMove);
13712
+ this.on(doc, 'touchend', this.handleMouseUp);
13713
+
13714
+ this.handleMouseMove(event);
13715
+ };
13716
+
13717
+ /**
13718
+ * Handle the `mousemove`, `touchmove`, and `mousedown` events on this `Slider`.
13719
+ * The `mousemove` and `touchmove` events will only only trigger this function during
13720
+ * `mousedown` and `touchstart`. This is due to {@link Slider#handleMouseDown} and
13721
+ * {@link Slider#handleMouseUp}.
13722
+ *
13723
+ * @param {EventTarget~Event} event
13724
+ * `mousedown`, `mousemove`, `touchstart`, or `touchmove` event that triggered
13725
+ * this function
13726
+ *
13727
+ * @listens mousemove
13728
+ * @listens touchmove
13729
+ */
13730
+
13731
+
13732
+ Slider.prototype.handleMouseMove = function handleMouseMove(event) {};
13733
+
13734
+ /**
13735
+ * Handle `mouseup` or `touchend` events on the `Slider`.
13736
+ *
13737
+ * @param {EventTarget~Event} event
13738
+ * `mouseup` or `touchend` event that triggered this function.
13739
+ *
13740
+ * @listens touchend
13741
+ * @listens mouseup
13742
+ * @fires Slider#sliderinactive
13743
+ */
13744
+
13745
+
13746
+ Slider.prototype.handleMouseUp = function handleMouseUp() {
13747
+ var doc = this.bar.el_.ownerDocument;
13748
+
13749
+ unblockTextSelection();
13750
+
13751
+ this.removeClass('vjs-sliding');
13752
+ /**
13753
+ * Triggered when the slider is no longer in an active state.
13754
+ *
13755
+ * @event Slider#sliderinactive
13756
+ * @type {EventTarget~Event}
13757
+ */
13758
+ this.trigger('sliderinactive');
13759
+
13760
+ this.off(doc, 'mousemove', this.handleMouseMove);
13761
+ this.off(doc, 'mouseup', this.handleMouseUp);
13762
+ this.off(doc, 'touchmove', this.handleMouseMove);
13763
+ this.off(doc, 'touchend', this.handleMouseUp);
13764
+
13765
+ this.update();
13766
+ };
13767
+
13768
+ /**
13769
+ * Update the progress bar of the `Slider`.
13770
+ *
13771
+ * @returns {number}
13772
+ * The percentage of progress the progress bar represents as a
13773
+ * number from 0 to 1.
13774
+ */
13775
+
13776
+
13777
+ Slider.prototype.update = function update() {
13778
+
13779
+ // In VolumeBar init we have a setTimeout for update that pops and update
13780
+ // to the end of the execution stack. The player is destroyed before then
13781
+ // update will cause an error
13782
+ if (!this.el_) {
13783
+ return;
13784
+ }
13785
+
13786
+ // If scrubbing, we could use a cached value to make the handle keep up
13787
+ // with the user's mouse. On HTML5 browsers scrubbing is really smooth, but
13788
+ // some flash players are slow, so we might want to utilize this later.
13789
+ // var progress = (this.player_.scrubbing()) ? this.player_.getCache().currentTime / this.player_.duration() : this.player_.currentTime() / this.player_.duration();
13790
+ var progress = this.getPercent();
13791
+ var bar = this.bar;
13792
+
13793
+ // If there's no bar...
13794
+ if (!bar) {
13795
+ return;
13796
+ }
13797
+
13798
+ // Protect against no duration and other division issues
13799
+ if (typeof progress !== 'number' || progress !== progress || progress < 0 || progress === Infinity) {
13800
+ progress = 0;
13801
+ }
13802
+
13803
+ // Convert to a percentage for setting
13804
+ var percentage = (progress * 100).toFixed(2) + '%';
13805
+ var style = bar.el().style;
13806
+
13807
+ // Set the new bar width or height
13808
+ if (this.vertical()) {
13809
+ style.height = percentage;
13810
+ } else {
13811
+ style.width = percentage;
13812
+ }
13813
+
13814
+ return progress;
13815
+ };
13816
+
13817
+ /**
13818
+ * Calculate distance for slider
13819
+ *
13820
+ * @param {EventTarget~Event} event
13821
+ * The event that caused this function to run.
13822
+ *
13823
+ * @return {number}
13824
+ * The current position of the Slider.
13825
+ * - position.x for vertical `Slider`s
13826
+ * - position.y for horizontal `Slider`s
13827
+ */
13828
+
13829
+
13830
+ Slider.prototype.calculateDistance = function calculateDistance(event) {
13831
+ var position = getPointerPosition(this.el_, event);
13832
+
13833
+ if (this.vertical()) {
13834
+ return position.y;
13835
+ }
13836
+ return position.x;
13837
+ };
13838
+
13839
+ /**
13840
+ * Handle a `focus` event on this `Slider`.
13841
+ *
13842
+ * @param {EventTarget~Event} event
13843
+ * The `focus` event that caused this function to run.
13844
+ *
13845
+ * @listens focus
13846
+ */
13847
+
13848
+
13849
+ Slider.prototype.handleFocus = function handleFocus() {
13850
+ this.on(this.bar.el_.ownerDocument, 'keydown', this.handleKeyPress);
13851
+ };
13852
+
13853
+ /**
13854
+ * Handle a `keydown` event on the `Slider`. Watches for left, rigth, up, and down
13855
+ * arrow keys. This function will only be called when the slider has focus. See
13856
+ * {@link Slider#handleFocus} and {@link Slider#handleBlur}.
13857
+ *
13858
+ * @param {EventTarget~Event} event
13859
+ * the `keydown` event that caused this function to run.
13860
+ *
13861
+ * @listens keydown
13862
+ */
13863
+
13864
+
13865
+ Slider.prototype.handleKeyPress = function handleKeyPress(event) {
13866
+ // Left and Down Arrows
13867
+ if (event.which === 37 || event.which === 40) {
13868
+ event.preventDefault();
13869
+ this.stepBack();
13870
+
13871
+ // Up and Right Arrows
13872
+ } else if (event.which === 38 || event.which === 39) {
13873
+ event.preventDefault();
13874
+ this.stepForward();
13875
+ }
13876
+ };
13877
+
13878
+ /**
13879
+ * Handle a `blur` event on this `Slider`.
13880
+ *
13881
+ * @param {EventTarget~Event} event
13882
+ * The `blur` event that caused this function to run.
13883
+ *
13884
+ * @listens blur
13885
+ */
13886
+
13887
+ Slider.prototype.handleBlur = function handleBlur() {
13888
+ this.off(this.bar.el_.ownerDocument, 'keydown', this.handleKeyPress);
13889
+ };
13890
+
13891
+ /**
13892
+ * Listener for click events on slider, used to prevent clicks
13893
+ * from bubbling up to parent elements like button menus.
13894
+ *
13895
+ * @param {Object} event
13896
+ * Event that caused this object to run
13897
+ */
13898
+
13899
+
13900
+ Slider.prototype.handleClick = function handleClick(event) {
13901
+ event.stopImmediatePropagation();
13902
+ event.preventDefault();
13903
+ };
13904
+
13905
+ /**
13906
+ * Get/set if slider is horizontal for vertical
13907
+ *
13908
+ * @param {boolean} [bool]
13909
+ * - true if slider is vertical,
13910
+ * - false is horizontal
13911
+ *
13912
+ * @return {boolean}
13913
+ * - true if slider is vertical, and getting
13914
+ * - false if the slider is horizontal, and getting
13915
+ */
13916
+
13917
+
13918
+ Slider.prototype.vertical = function vertical(bool) {
13919
+ if (bool === undefined) {
13920
+ return this.vertical_ || false;
13921
+ }
13922
+
13923
+ this.vertical_ = !!bool;
13924
+
13925
+ if (this.vertical_) {
13926
+ this.addClass('vjs-slider-vertical');
13927
+ } else {
13928
+ this.addClass('vjs-slider-horizontal');
13929
+ }
13930
+ };
13931
+
13932
+ return Slider;
13933
+ }(Component);
13934
+
13935
+ Component.registerComponent('Slider', Slider);
13936
+
13937
+ /**
13938
+ * @file load-progress-bar.js
13939
+ */
13940
+
13941
+ /**
13942
+ * Shows loading progress
13943
+ *
13944
+ * @extends Component
13945
+ */
13946
+
13947
+ var LoadProgressBar = function (_Component) {
13948
+ inherits(LoadProgressBar, _Component);
13949
+
13950
+ /**
13951
+ * Creates an instance of this class.
13952
+ *
13953
+ * @param {Player} player
13954
+ * The `Player` that this class should be attached to.
13955
+ *
13956
+ * @param {Object} [options]
13957
+ * The key/value store of player options.
13958
+ */
13959
+ function LoadProgressBar(player, options) {
13960
+ classCallCheck(this, LoadProgressBar);
13961
+
13962
+ var _this = possibleConstructorReturn(this, _Component.call(this, player, options));
13963
+
13964
+ _this.partEls_ = [];
13965
+ _this.on(player, 'progress', _this.update);
13966
+ return _this;
13967
+ }
13968
+
13969
+ /**
13970
+ * Create the `Component`'s DOM element
13971
+ *
13972
+ * @return {Element}
13973
+ * The element that was created.
13974
+ */
13975
+
13976
+
13977
+ LoadProgressBar.prototype.createEl = function createEl$$1() {
13978
+ return _Component.prototype.createEl.call(this, 'div', {
13979
+ className: 'vjs-load-progress',
13980
+ innerHTML: '<span class="vjs-control-text"><span>' + this.localize('Loaded') + '</span>: 0%</span>'
13981
+ });
13982
+ };
13983
+
13984
+ LoadProgressBar.prototype.dispose = function dispose() {
13985
+ this.partEls_ = null;
13986
+
13987
+ _Component.prototype.dispose.call(this);
13988
+ };
13989
+
13990
+ /**
13991
+ * Update progress bar
13992
+ *
13993
+ * @param {EventTarget~Event} [event]
13994
+ * The `progress` event that caused this function to run.
13995
+ *
13996
+ * @listens Player#progress
13997
+ */
13998
+
13999
+
14000
+ LoadProgressBar.prototype.update = function update(event) {
14001
+ var buffered = this.player_.buffered();
14002
+ var duration = this.player_.duration();
14003
+ var bufferedEnd = this.player_.bufferedEnd();
14004
+ var children = this.partEls_;
14005
+
14006
+ // get the percent width of a time compared to the total end
14007
+ var percentify = function percentify(time, end) {
14008
+ // no NaN
14009
+ var percent = time / end || 0;
14010
+
14011
+ return (percent >= 1 ? 1 : percent) * 100 + '%';
14012
+ };
14013
+
14014
+ // update the width of the progress bar
14015
+ this.el_.style.width = percentify(bufferedEnd, duration);
14016
+
14017
+ // add child elements to represent the individual buffered time ranges
14018
+ for (var i = 0; i < buffered.length; i++) {
14019
+ var start = buffered.start(i);
14020
+ var end = buffered.end(i);
14021
+ var part = children[i];
14022
+
14023
+ if (!part) {
14024
+ part = this.el_.appendChild(createEl());
14025
+ children[i] = part;
14026
+ }
14027
+
14028
+ // set the percent based on the width of the progress bar (bufferedEnd)
14029
+ part.style.left = percentify(start, bufferedEnd);
14030
+ part.style.width = percentify(end - start, bufferedEnd);
14031
+ }
14032
+
14033
+ // remove unused buffered range elements
14034
+ for (var _i = children.length; _i > buffered.length; _i--) {
14035
+ this.el_.removeChild(children[_i - 1]);
14036
+ }
14037
+ children.length = buffered.length;
14038
+ };
14039
+
14040
+ return LoadProgressBar;
14041
+ }(Component);
14042
+
14043
+ Component.registerComponent('LoadProgressBar', LoadProgressBar);
14044
+
14045
+ /**
14046
+ * @file time-tooltip.js
14047
+ */
14048
+
14049
+ /**
14050
+ * Time tooltips display a time above the progress bar.
14051
+ *
14052
+ * @extends Component
14053
+ */
14054
+
14055
+ var TimeTooltip = function (_Component) {
14056
+ inherits(TimeTooltip, _Component);
14057
+
14058
+ function TimeTooltip() {
14059
+ classCallCheck(this, TimeTooltip);
14060
+ return possibleConstructorReturn(this, _Component.apply(this, arguments));
14061
+ }
14062
+
14063
+ /**
14064
+ * Create the time tooltip DOM element
14065
+ *
14066
+ * @return {Element}
14067
+ * The element that was created.
14068
+ */
14069
+ TimeTooltip.prototype.createEl = function createEl$$1() {
14070
+ return _Component.prototype.createEl.call(this, 'div', {
14071
+ className: 'vjs-time-tooltip'
14072
+ });
14073
+ };
14074
+
14075
+ /**
14076
+ * Updates the position of the time tooltip relative to the `SeekBar`.
14077
+ *
14078
+ * @param {Object} seekBarRect
14079
+ * The `ClientRect` for the {@link SeekBar} element.
14080
+ *
14081
+ * @param {number} seekBarPoint
14082
+ * A number from 0 to 1, representing a horizontal reference point
14083
+ * from the left edge of the {@link SeekBar}
14084
+ */
14085
+
14086
+
14087
+ TimeTooltip.prototype.update = function update(seekBarRect, seekBarPoint, content) {
14088
+ var tooltipRect = getBoundingClientRect(this.el_);
14089
+ var playerRect = getBoundingClientRect(this.player_.el());
14090
+ var seekBarPointPx = seekBarRect.width * seekBarPoint;
14091
+
14092
+ // do nothing if either rect isn't available
14093
+ // for example, if the player isn't in the DOM for testing
14094
+ if (!playerRect || !tooltipRect) {
14095
+ return;
14096
+ }
14097
+
14098
+ // This is the space left of the `seekBarPoint` available within the bounds
14099
+ // of the player. We calculate any gap between the left edge of the player
14100
+ // and the left edge of the `SeekBar` and add the number of pixels in the
14101
+ // `SeekBar` before hitting the `seekBarPoint`
14102
+ var spaceLeftOfPoint = seekBarRect.left - playerRect.left + seekBarPointPx;
14103
+
14104
+ // This is the space right of the `seekBarPoint` available within the bounds
14105
+ // of the player. We calculate the number of pixels from the `seekBarPoint`
14106
+ // to the right edge of the `SeekBar` and add to that any gap between the
14107
+ // right edge of the `SeekBar` and the player.
14108
+ var spaceRightOfPoint = seekBarRect.width - seekBarPointPx + (playerRect.right - seekBarRect.right);
14109
+
14110
+ // This is the number of pixels by which the tooltip will need to be pulled
14111
+ // further to the right to center it over the `seekBarPoint`.
14112
+ var pullTooltipBy = tooltipRect.width / 2;
14113
+
14114
+ // Adjust the `pullTooltipBy` distance to the left or right depending on
14115
+ // the results of the space calculations above.
14116
+ if (spaceLeftOfPoint < pullTooltipBy) {
14117
+ pullTooltipBy += pullTooltipBy - spaceLeftOfPoint;
14118
+ } else if (spaceRightOfPoint < pullTooltipBy) {
14119
+ pullTooltipBy = spaceRightOfPoint;
14120
+ }
14121
+
14122
+ // Due to the imprecision of decimal/ratio based calculations and varying
14123
+ // rounding behaviors, there are cases where the spacing adjustment is off
14124
+ // by a pixel or two. This adds insurance to these calculations.
14125
+ if (pullTooltipBy < 0) {
14126
+ pullTooltipBy = 0;
14127
+ } else if (pullTooltipBy > tooltipRect.width) {
14128
+ pullTooltipBy = tooltipRect.width;
14129
+ }
14130
+
14131
+ this.el_.style.right = '-' + pullTooltipBy + 'px';
14132
+ textContent(this.el_, content);
14133
+ };
14134
+
14135
+ return TimeTooltip;
14136
+ }(Component);
14137
+
14138
+ Component.registerComponent('TimeTooltip', TimeTooltip);
14139
+
14140
+ /**
14141
+ * @file play-progress-bar.js
14142
+ */
14143
+
14144
+ /**
14145
+ * Used by {@link SeekBar} to display media playback progress as part of the
14146
+ * {@link ProgressControl}.
14147
+ *
14148
+ * @extends Component
14149
+ */
14150
+
14151
+ var PlayProgressBar = function (_Component) {
14152
+ inherits(PlayProgressBar, _Component);
14153
+
14154
+ function PlayProgressBar() {
14155
+ classCallCheck(this, PlayProgressBar);
14156
+ return possibleConstructorReturn(this, _Component.apply(this, arguments));
14157
+ }
14158
+
14159
+ /**
14160
+ * Create the the DOM element for this class.
14161
+ *
14162
+ * @return {Element}
14163
+ * The element that was created.
14164
+ */
14165
+ PlayProgressBar.prototype.createEl = function createEl() {
14166
+ return _Component.prototype.createEl.call(this, 'div', {
14167
+ className: 'vjs-play-progress vjs-slider-bar',
14168
+ innerHTML: '<span class="vjs-control-text"><span>' + this.localize('Progress') + '</span>: 0%</span>'
14169
+ });
14170
+ };
14171
+
14172
+ /**
14173
+ * Enqueues updates to its own DOM as well as the DOM of its
14174
+ * {@link TimeTooltip} child.
14175
+ *
14176
+ * @param {Object} seekBarRect
14177
+ * The `ClientRect` for the {@link SeekBar} element.
14178
+ *
14179
+ * @param {number} seekBarPoint
14180
+ * A number from 0 to 1, representing a horizontal reference point
14181
+ * from the left edge of the {@link SeekBar}
14182
+ */
14183
+
14184
+
14185
+ PlayProgressBar.prototype.update = function update(seekBarRect, seekBarPoint) {
14186
+ var _this2 = this;
14187
+
14188
+ // If there is an existing rAF ID, cancel it so we don't over-queue.
14189
+ if (this.rafId_) {
14190
+ this.cancelAnimationFrame(this.rafId_);
14191
+ }
14192
+
14193
+ this.rafId_ = this.requestAnimationFrame(function () {
14194
+ var time = _this2.player_.scrubbing() ? _this2.player_.getCache().currentTime : _this2.player_.currentTime();
14195
+
14196
+ var content = formatTime(time, _this2.player_.duration());
14197
+ var timeTooltip = _this2.getChild('timeTooltip');
14198
+
14199
+ if (timeTooltip) {
14200
+ timeTooltip.update(seekBarRect, seekBarPoint, content);
14201
+ }
14202
+ });
14203
+ };
14204
+
14205
+ return PlayProgressBar;
14206
+ }(Component);
14207
+
14208
+ /**
14209
+ * Default options for {@link PlayProgressBar}.
14210
+ *
14211
+ * @type {Object}
14212
+ * @private
14213
+ */
14214
+
14215
+
14216
+ PlayProgressBar.prototype.options_ = {
14217
+ children: []
14218
+ };
14219
+
14220
+ // Time tooltips should not be added to a player on mobile devices
14221
+ if (!IS_IOS && !IS_ANDROID) {
14222
+ PlayProgressBar.prototype.options_.children.push('timeTooltip');
14223
+ }
14224
+
14225
+ Component.registerComponent('PlayProgressBar', PlayProgressBar);
14226
+
14227
+ /**
14228
+ * @file mouse-time-display.js
14229
+ */
14230
+
14231
+ /**
14232
+ * The {@link MouseTimeDisplay} component tracks mouse movement over the
14233
+ * {@link ProgressControl}. It displays an indicator and a {@link TimeTooltip}
14234
+ * indicating the time which is represented by a given point in the
14235
+ * {@link ProgressControl}.
14236
+ *
14237
+ * @extends Component
14238
+ */
14239
+
14240
+ var MouseTimeDisplay = function (_Component) {
14241
+ inherits(MouseTimeDisplay, _Component);
14242
+
14243
+ /**
14244
+ * Creates an instance of this class.
14245
+ *
14246
+ * @param {Player} player
14247
+ * The {@link Player} that this class should be attached to.
14248
+ *
14249
+ * @param {Object} [options]
14250
+ * The key/value store of player options.
14251
+ */
14252
+ function MouseTimeDisplay(player, options) {
14253
+ classCallCheck(this, MouseTimeDisplay);
14254
+
14255
+ var _this = possibleConstructorReturn(this, _Component.call(this, player, options));
14256
+
14257
+ _this.update = throttle(bind(_this, _this.update), 25);
14258
+ return _this;
14259
+ }
14260
+
14261
+ /**
14262
+ * Create the DOM element for this class.
14263
+ *
14264
+ * @return {Element}
14265
+ * The element that was created.
14266
+ */
14267
+
14268
+
14269
+ MouseTimeDisplay.prototype.createEl = function createEl() {
14270
+ return _Component.prototype.createEl.call(this, 'div', {
14271
+ className: 'vjs-mouse-display'
14272
+ });
14273
+ };
14274
+
14275
+ /**
14276
+ * Enqueues updates to its own DOM as well as the DOM of its
14277
+ * {@link TimeTooltip} child.
14278
+ *
14279
+ * @param {Object} seekBarRect
14280
+ * The `ClientRect` for the {@link SeekBar} element.
14281
+ *
14282
+ * @param {number} seekBarPoint
14283
+ * A number from 0 to 1, representing a horizontal reference point
14284
+ * from the left edge of the {@link SeekBar}
14285
+ */
14286
+
14287
+
14288
+ MouseTimeDisplay.prototype.update = function update(seekBarRect, seekBarPoint) {
14289
+ var _this2 = this;
14290
+
14291
+ // If there is an existing rAF ID, cancel it so we don't over-queue.
14292
+ if (this.rafId_) {
14293
+ this.cancelAnimationFrame(this.rafId_);
14294
+ }
14295
+
14296
+ this.rafId_ = this.requestAnimationFrame(function () {
14297
+ var duration = _this2.player_.duration();
14298
+ var content = formatTime(seekBarPoint * duration, duration);
14299
+
14300
+ _this2.el_.style.left = seekBarRect.width * seekBarPoint + 'px';
14301
+ _this2.getChild('timeTooltip').update(seekBarRect, seekBarPoint, content);
14302
+ });
14303
+ };
14304
+
14305
+ return MouseTimeDisplay;
14306
+ }(Component);
14307
+
14308
+ /**
14309
+ * Default options for `MouseTimeDisplay`
14310
+ *
14311
+ * @type {Object}
14312
+ * @private
14313
+ */
14314
+
14315
+
14316
+ MouseTimeDisplay.prototype.options_ = {
14317
+ children: ['timeTooltip']
14318
+ };
14319
+
14320
+ Component.registerComponent('MouseTimeDisplay', MouseTimeDisplay);
14321
+
14322
+ /**
14323
+ * @file seek-bar.js
14324
+ */
14325
+
14326
+ // The number of seconds the `step*` functions move the timeline.
14327
+ var STEP_SECONDS = 5;
14328
+
14329
+ // The interval at which the bar should update as it progresses.
14330
+ var UPDATE_REFRESH_INTERVAL = 30;
14331
+
14332
+ /**
14333
+ * Seek bar and container for the progress bars. Uses {@link PlayProgressBar}
14334
+ * as its `bar`.
14335
+ *
14336
+ * @extends Slider
14337
+ */
14338
+
14339
+ var SeekBar = function (_Slider) {
14340
+ inherits(SeekBar, _Slider);
14341
+
14342
+ /**
14343
+ * Creates an instance of this class.
14344
+ *
14345
+ * @param {Player} player
14346
+ * The `Player` that this class should be attached to.
14347
+ *
14348
+ * @param {Object} [options]
14349
+ * The key/value store of player options.
14350
+ */
14351
+ function SeekBar(player, options) {
14352
+ classCallCheck(this, SeekBar);
14353
+
14354
+ var _this = possibleConstructorReturn(this, _Slider.call(this, player, options));
14355
+
14356
+ _this.setEventHandlers_();
14357
+ return _this;
14358
+ }
14359
+
14360
+ /**
14361
+ * Sets the event handlers
14362
+ *
14363
+ * @private
14364
+ */
14365
+
14366
+
14367
+ SeekBar.prototype.setEventHandlers_ = function setEventHandlers_() {
14368
+ var _this2 = this;
14369
+
14370
+ this.update = throttle(bind(this, this.update), UPDATE_REFRESH_INTERVAL);
14371
+
14372
+ this.on(this.player_, 'timeupdate', this.update);
14373
+ this.on(this.player_, 'ended', this.handleEnded);
14374
+
14375
+ // when playing, let's ensure we smoothly update the play progress bar
14376
+ // via an interval
14377
+ this.updateInterval = null;
14378
+
14379
+ this.on(this.player_, ['playing'], function () {
14380
+ _this2.clearInterval(_this2.updateInterval);
14381
+
14382
+ _this2.updateInterval = _this2.setInterval(function () {
14383
+ _this2.requestAnimationFrame(function () {
14384
+ _this2.update();
14385
+ });
14386
+ }, UPDATE_REFRESH_INTERVAL);
14387
+ });
14388
+
14389
+ this.on(this.player_, ['ended', 'pause', 'waiting'], function () {
14390
+ _this2.clearInterval(_this2.updateInterval);
14391
+ });
14392
+
14393
+ this.on(this.player_, ['timeupdate', 'ended'], this.update);
14394
+ };
14395
+
14396
+ /**
14397
+ * Create the `Component`'s DOM element
14398
+ *
14399
+ * @return {Element}
14400
+ * The element that was created.
14401
+ */
14402
+
14403
+
14404
+ SeekBar.prototype.createEl = function createEl$$1() {
14405
+ return _Slider.prototype.createEl.call(this, 'div', {
14406
+ className: 'vjs-progress-holder'
14407
+ }, {
14408
+ 'aria-label': this.localize('Progress Bar')
14409
+ });
14410
+ };
14411
+
14412
+ /**
14413
+ * This function updates the play progress bar and accessibility
14414
+ * attributes to whatever is passed in.
14415
+ *
14416
+ * @param {number} currentTime
14417
+ * The currentTime value that should be used for accessibility
14418
+ *
14419
+ * @param {number} percent
14420
+ * The percentage as a decimal that the bar should be filled from 0-1.
14421
+ *
14422
+ * @private
14423
+ */
14424
+
14425
+
14426
+ SeekBar.prototype.update_ = function update_(currentTime, percent) {
14427
+ var duration = this.player_.duration();
14428
+
14429
+ // machine readable value of progress bar (percentage complete)
14430
+ this.el_.setAttribute('aria-valuenow', (percent * 100).toFixed(2));
14431
+
14432
+ // human readable value of progress bar (time complete)
14433
+ this.el_.setAttribute('aria-valuetext', this.localize('progress bar timing: currentTime={1} duration={2}', [formatTime(currentTime, duration), formatTime(duration, duration)], '{1} of {2}'));
14434
+
14435
+ // Update the `PlayProgressBar`.
14436
+ this.bar.update(getBoundingClientRect(this.el_), percent);
14437
+ };
14438
+
14439
+ /**
14440
+ * Update the seek bar's UI.
14441
+ *
14442
+ * @param {EventTarget~Event} [event]
14443
+ * The `timeupdate` or `ended` event that caused this to run.
14444
+ *
14445
+ * @listens Player#timeupdate
14446
+ *
14447
+ * @returns {number}
14448
+ * The current percent at a number from 0-1
14449
+ */
14450
+
14451
+
14452
+ SeekBar.prototype.update = function update(event) {
14453
+ var percent = _Slider.prototype.update.call(this);
14454
+
14455
+ this.update_(this.getCurrentTime_(), percent);
14456
+ return percent;
14457
+ };
14458
+
14459
+ /**
14460
+ * Get the value of current time but allows for smooth scrubbing,
14461
+ * when player can't keep up.
14462
+ *
14463
+ * @return {number}
14464
+ * The current time value to display
14465
+ *
14466
+ * @private
14467
+ */
14468
+
14469
+
14470
+ SeekBar.prototype.getCurrentTime_ = function getCurrentTime_() {
14471
+ return this.player_.scrubbing() ? this.player_.getCache().currentTime : this.player_.currentTime();
14472
+ };
14473
+
14474
+ /**
14475
+ * We want the seek bar to be full on ended
14476
+ * no matter what the actual internal values are. so we force it.
14477
+ *
14478
+ * @param {EventTarget~Event} [event]
14479
+ * The `timeupdate` or `ended` event that caused this to run.
14480
+ *
14481
+ * @listens Player#ended
14482
+ */
14483
+
14484
+
14485
+ SeekBar.prototype.handleEnded = function handleEnded(event) {
14486
+ this.update_(this.player_.duration(), 1);
14487
+ };
14488
+
14489
+ /**
14490
+ * Get the percentage of media played so far.
14491
+ *
14492
+ * @return {number}
14493
+ * The percentage of media played so far (0 to 1).
14494
+ */
14495
+
14496
+
14497
+ SeekBar.prototype.getPercent = function getPercent() {
14498
+ var percent = this.getCurrentTime_() / this.player_.duration();
14499
+
14500
+ return percent >= 1 ? 1 : percent || 0;
14501
+ };
14502
+
14503
+ /**
14504
+ * Handle mouse down on seek bar
14505
+ *
14506
+ * @param {EventTarget~Event} event
14507
+ * The `mousedown` event that caused this to run.
14508
+ *
14509
+ * @listens mousedown
14510
+ */
14511
+
14512
+
14513
+ SeekBar.prototype.handleMouseDown = function handleMouseDown(event) {
14514
+ if (!isSingleLeftClick(event)) {
14515
+ return;
14516
+ }
14517
+
14518
+ // Stop event propagation to prevent double fire in progress-control.js
14519
+ event.stopPropagation();
14520
+ this.player_.scrubbing(true);
14521
+
14522
+ this.videoWasPlaying = !this.player_.paused();
14523
+ this.player_.pause();
14524
+
14525
+ _Slider.prototype.handleMouseDown.call(this, event);
14526
+ };
14527
+
14528
+ /**
14529
+ * Handle mouse move on seek bar
14530
+ *
14531
+ * @param {EventTarget~Event} event
14532
+ * The `mousemove` event that caused this to run.
14533
+ *
14534
+ * @listens mousemove
14535
+ */
14536
+
14537
+
14538
+ SeekBar.prototype.handleMouseMove = function handleMouseMove(event) {
14539
+ if (!isSingleLeftClick(event)) {
14540
+ return;
14541
+ }
14542
+
14543
+ var newTime = this.calculateDistance(event) * this.player_.duration();
14544
+
14545
+ // Don't let video end while scrubbing.
14546
+ if (newTime === this.player_.duration()) {
14547
+ newTime = newTime - 0.1;
14548
+ }
14549
+
14550
+ // Set new time (tell player to seek to new time)
14551
+ this.player_.currentTime(newTime);
14552
+ };
14553
+
14554
+ SeekBar.prototype.enable = function enable() {
14555
+ _Slider.prototype.enable.call(this);
14556
+ var mouseTimeDisplay = this.getChild('mouseTimeDisplay');
14557
+
14558
+ if (!mouseTimeDisplay) {
14559
+ return;
14560
+ }
14561
+
14562
+ mouseTimeDisplay.show();
14563
+ };
14564
+
14565
+ SeekBar.prototype.disable = function disable() {
14566
+ _Slider.prototype.disable.call(this);
14567
+ var mouseTimeDisplay = this.getChild('mouseTimeDisplay');
14568
+
14569
+ if (!mouseTimeDisplay) {
14570
+ return;
14571
+ }
14572
+
14573
+ mouseTimeDisplay.hide();
14574
+ };
14575
+
14576
+ /**
14577
+ * Handle mouse up on seek bar
14578
+ *
14579
+ * @param {EventTarget~Event} event
14580
+ * The `mouseup` event that caused this to run.
14581
+ *
14582
+ * @listens mouseup
14583
+ */
14584
+
14585
+
14586
+ SeekBar.prototype.handleMouseUp = function handleMouseUp(event) {
14587
+ _Slider.prototype.handleMouseUp.call(this, event);
14588
+
14589
+ // Stop event propagation to prevent double fire in progress-control.js
14590
+ if (event) {
14591
+ event.stopPropagation();
14592
+ }
14593
+ this.player_.scrubbing(false);
14594
+
14595
+ /**
14596
+ * Trigger timeupdate because we're done seeking and the time has changed.
14597
+ * This is particularly useful for if the player is paused to time the time displays.
14598
+ *
14599
+ * @event Tech#timeupdate
14600
+ * @type {EventTarget~Event}
14601
+ */
14602
+ this.player_.trigger({ type: 'timeupdate', target: this, manuallyTriggered: true });
14603
+ if (this.videoWasPlaying) {
14604
+ silencePromise(this.player_.play());
14605
+ }
14606
+ };
14607
+
14608
+ /**
14609
+ * Move more quickly fast forward for keyboard-only users
14610
+ */
14611
+
14612
+
14613
+ SeekBar.prototype.stepForward = function stepForward() {
14614
+ this.player_.currentTime(this.player_.currentTime() + STEP_SECONDS);
14615
+ };
14616
+
14617
+ /**
14618
+ * Move more quickly rewind for keyboard-only users
14619
+ */
14620
+
14621
+
14622
+ SeekBar.prototype.stepBack = function stepBack() {
14623
+ this.player_.currentTime(this.player_.currentTime() - STEP_SECONDS);
14624
+ };
14625
+
14626
+ /**
14627
+ * Toggles the playback state of the player
14628
+ * This gets called when enter or space is used on the seekbar
14629
+ *
14630
+ * @param {EventTarget~Event} event
14631
+ * The `keydown` event that caused this function to be called
14632
+ *
14633
+ */
14634
+
14635
+
14636
+ SeekBar.prototype.handleAction = function handleAction(event) {
14637
+ if (this.player_.paused()) {
14638
+ this.player_.play();
14639
+ } else {
14640
+ this.player_.pause();
14641
+ }
14642
+ };
14643
+
14644
+ /**
14645
+ * Called when this SeekBar has focus and a key gets pressed down. By
14646
+ * default it will call `this.handleAction` when the key is space or enter.
14647
+ *
14648
+ * @param {EventTarget~Event} event
14649
+ * The `keydown` event that caused this function to be called.
14650
+ *
14651
+ * @listens keydown
14652
+ */
14653
+
14654
+
14655
+ SeekBar.prototype.handleKeyPress = function handleKeyPress(event) {
14656
+
14657
+ // Support Space (32) or Enter (13) key operation to fire a click event
14658
+ if (event.which === 32 || event.which === 13) {
14659
+ event.preventDefault();
14660
+ this.handleAction(event);
14661
+ } else if (_Slider.prototype.handleKeyPress) {
14662
+
14663
+ // Pass keypress handling up for unsupported keys
14664
+ _Slider.prototype.handleKeyPress.call(this, event);
14665
+ }
14666
+ };
14667
+
14668
+ return SeekBar;
14669
+ }(Slider);
14670
+
14671
+ /**
14672
+ * Default options for the `SeekBar`
14673
+ *
14674
+ * @type {Object}
14675
+ * @private
14676
+ */
14677
+
14678
+
14679
+ SeekBar.prototype.options_ = {
14680
+ children: ['loadProgressBar', 'playProgressBar'],
14681
+ barName: 'playProgressBar'
14682
+ };
14683
+
14684
+ // MouseTimeDisplay tooltips should not be added to a player on mobile devices
14685
+ if (!IS_IOS && !IS_ANDROID) {
14686
+ SeekBar.prototype.options_.children.splice(1, 0, 'mouseTimeDisplay');
14687
+ }
14688
+
14689
+ /**
14690
+ * Call the update event for this Slider when this event happens on the player.
14691
+ *
14692
+ * @type {string}
14693
+ */
14694
+ SeekBar.prototype.playerEvent = 'timeupdate';
14695
+
14696
+ Component.registerComponent('SeekBar', SeekBar);
14697
+
14698
+ /**
14699
+ * @file progress-control.js
14700
+ */
14701
+
14702
+ /**
14703
+ * The Progress Control component contains the seek bar, load progress,
14704
+ * and play progress.
14705
+ *
14706
+ * @extends Component
14707
+ */
14708
+
14709
+ var ProgressControl = function (_Component) {
14710
+ inherits(ProgressControl, _Component);
14711
+
14712
+ /**
14713
+ * Creates an instance of this class.
14714
+ *
14715
+ * @param {Player} player
14716
+ * The `Player` that this class should be attached to.
14717
+ *
14718
+ * @param {Object} [options]
14719
+ * The key/value store of player options.
14720
+ */
14721
+ function ProgressControl(player, options) {
14722
+ classCallCheck(this, ProgressControl);
14723
+
14724
+ var _this = possibleConstructorReturn(this, _Component.call(this, player, options));
14725
+
14726
+ _this.handleMouseMove = throttle(bind(_this, _this.handleMouseMove), 25);
14727
+ _this.throttledHandleMouseSeek = throttle(bind(_this, _this.handleMouseSeek), 25);
14728
+
14729
+ _this.enable();
14730
+ return _this;
14731
+ }
14732
+
14733
+ /**
14734
+ * Create the `Component`'s DOM element
14735
+ *
14736
+ * @return {Element}
14737
+ * The element that was created.
14738
+ */
14739
+
14740
+
14741
+ ProgressControl.prototype.createEl = function createEl$$1() {
14742
+ return _Component.prototype.createEl.call(this, 'div', {
14743
+ className: 'vjs-progress-control vjs-control'
14744
+ });
14745
+ };
14746
+
14747
+ /**
14748
+ * When the mouse moves over the `ProgressControl`, the pointer position
14749
+ * gets passed down to the `MouseTimeDisplay` component.
14750
+ *
14751
+ * @param {EventTarget~Event} event
14752
+ * The `mousemove` event that caused this function to run.
14753
+ *
14754
+ * @listen mousemove
14755
+ */
14756
+
14757
+
14758
+ ProgressControl.prototype.handleMouseMove = function handleMouseMove(event) {
14759
+ var seekBar = this.getChild('seekBar');
14760
+
14761
+ if (seekBar) {
14762
+ var mouseTimeDisplay = seekBar.getChild('mouseTimeDisplay');
14763
+ var seekBarEl = seekBar.el();
14764
+ var seekBarRect = getBoundingClientRect(seekBarEl);
14765
+ var seekBarPoint = getPointerPosition(seekBarEl, event).x;
14766
+
14767
+ // The default skin has a gap on either side of the `SeekBar`. This means
14768
+ // that it's possible to trigger this behavior outside the boundaries of
14769
+ // the `SeekBar`. This ensures we stay within it at all times.
14770
+ if (seekBarPoint > 1) {
14771
+ seekBarPoint = 1;
14772
+ } else if (seekBarPoint < 0) {
14773
+ seekBarPoint = 0;
14774
+ }
14775
+
14776
+ if (mouseTimeDisplay) {
14777
+ mouseTimeDisplay.update(seekBarRect, seekBarPoint);
14778
+ }
14779
+ }
14780
+ };
14781
+
14782
+ /**
14783
+ * A throttled version of the {@link ProgressControl#handleMouseSeek} listener.
14784
+ *
14785
+ * @method ProgressControl#throttledHandleMouseSeek
14786
+ * @param {EventTarget~Event} event
14787
+ * The `mousemove` event that caused this function to run.
14788
+ *
14789
+ * @listen mousemove
14790
+ * @listen touchmove
14791
+ */
14792
+
14793
+ /**
14794
+ * Handle `mousemove` or `touchmove` events on the `ProgressControl`.
14795
+ *
14796
+ * @param {EventTarget~Event} event
14797
+ * `mousedown` or `touchstart` event that triggered this function
14798
+ *
14799
+ * @listens mousemove
14800
+ * @listens touchmove
14801
+ */
14802
+
14803
+
14804
+ ProgressControl.prototype.handleMouseSeek = function handleMouseSeek(event) {
14805
+ var seekBar = this.getChild('seekBar');
14806
+
14807
+ if (seekBar) {
14808
+ seekBar.handleMouseMove(event);
14809
+ }
14810
+ };
14811
+
14812
+ /**
14813
+ * Are controls are currently enabled for this progress control.
14814
+ *
14815
+ * @return {boolean}
14816
+ * true if controls are enabled, false otherwise
14817
+ */
14818
+
14819
+
14820
+ ProgressControl.prototype.enabled = function enabled() {
14821
+ return this.enabled_;
14822
+ };
14823
+
14824
+ /**
14825
+ * Disable all controls on the progress control and its children
14826
+ */
14827
+
14828
+
14829
+ ProgressControl.prototype.disable = function disable() {
14830
+ this.children().forEach(function (child) {
14831
+ return child.disable && child.disable();
14832
+ });
14833
+
14834
+ if (!this.enabled()) {
14835
+ return;
14836
+ }
14837
+
14838
+ this.off(['mousedown', 'touchstart'], this.handleMouseDown);
14839
+ this.off(this.el_, 'mousemove', this.handleMouseMove);
14840
+ this.handleMouseUp();
14841
+
14842
+ this.addClass('disabled');
14843
+
14844
+ this.enabled_ = false;
14845
+ };
14846
+
14847
+ /**
14848
+ * Enable all controls on the progress control and its children
14849
+ */
14850
+
14851
+
14852
+ ProgressControl.prototype.enable = function enable() {
14853
+ this.children().forEach(function (child) {
14854
+ return child.enable && child.enable();
14855
+ });
14856
+
14857
+ if (this.enabled()) {
14858
+ return;
14859
+ }
14860
+
14861
+ this.on(['mousedown', 'touchstart'], this.handleMouseDown);
14862
+ this.on(this.el_, 'mousemove', this.handleMouseMove);
14863
+ this.removeClass('disabled');
14864
+
14865
+ this.enabled_ = true;
14866
+ };
14867
+
14868
+ /**
14869
+ * Handle `mousedown` or `touchstart` events on the `ProgressControl`.
14870
+ *
14871
+ * @param {EventTarget~Event} event
14872
+ * `mousedown` or `touchstart` event that triggered this function
14873
+ *
14874
+ * @listens mousedown
14875
+ * @listens touchstart
14876
+ */
14877
+
14878
+
14879
+ ProgressControl.prototype.handleMouseDown = function handleMouseDown(event) {
14880
+ var doc = this.el_.ownerDocument;
14881
+ var seekBar = this.getChild('seekBar');
14882
+
14883
+ if (seekBar) {
14884
+ seekBar.handleMouseDown(event);
14885
+ }
14886
+
14887
+ this.on(doc, 'mousemove', this.throttledHandleMouseSeek);
14888
+ this.on(doc, 'touchmove', this.throttledHandleMouseSeek);
14889
+ this.on(doc, 'mouseup', this.handleMouseUp);
14890
+ this.on(doc, 'touchend', this.handleMouseUp);
14891
+ };
14892
+
14893
+ /**
14894
+ * Handle `mouseup` or `touchend` events on the `ProgressControl`.
14895
+ *
14896
+ * @param {EventTarget~Event} event
14897
+ * `mouseup` or `touchend` event that triggered this function.
14898
+ *
14899
+ * @listens touchend
14900
+ * @listens mouseup
14901
+ */
14902
+
14903
+
14904
+ ProgressControl.prototype.handleMouseUp = function handleMouseUp(event) {
14905
+ var doc = this.el_.ownerDocument;
14906
+ var seekBar = this.getChild('seekBar');
14907
+
14908
+ if (seekBar) {
14909
+ seekBar.handleMouseUp(event);
14910
+ }
14911
+
14912
+ this.off(doc, 'mousemove', this.throttledHandleMouseSeek);
14913
+ this.off(doc, 'touchmove', this.throttledHandleMouseSeek);
14914
+ this.off(doc, 'mouseup', this.handleMouseUp);
14915
+ this.off(doc, 'touchend', this.handleMouseUp);
14916
+ };
14917
+
14918
+ return ProgressControl;
14919
+ }(Component);
14920
+
14921
+ /**
14922
+ * Default options for `ProgressControl`
14923
+ *
14924
+ * @type {Object}
14925
+ * @private
14926
+ */
14927
+
14928
+
14929
+ ProgressControl.prototype.options_ = {
14930
+ children: ['seekBar']
14931
+ };
14932
+
14933
+ Component.registerComponent('ProgressControl', ProgressControl);
14934
+
14935
+ /**
14936
+ * @file fullscreen-toggle.js
14937
+ */
14938
+
14939
+ /**
14940
+ * Toggle fullscreen video
14941
+ *
14942
+ * @extends Button
14943
+ */
14944
+
14945
+ var FullscreenToggle = function (_Button) {
14946
+ inherits(FullscreenToggle, _Button);
14947
+
14948
+ /**
14949
+ * Creates an instance of this class.
14950
+ *
14951
+ * @param {Player} player
14952
+ * The `Player` that this class should be attached to.
14953
+ *
14954
+ * @param {Object} [options]
14955
+ * The key/value store of player options.
14956
+ */
14957
+ function FullscreenToggle(player, options) {
14958
+ classCallCheck(this, FullscreenToggle);
14959
+
14960
+ var _this = possibleConstructorReturn(this, _Button.call(this, player, options));
14961
+
14962
+ _this.on(player, 'fullscreenchange', _this.handleFullscreenChange);
14963
+
14964
+ if (document_1[FullscreenApi.fullscreenEnabled] === false) {
14965
+ _this.disable();
14966
+ }
14967
+ return _this;
14968
+ }
14969
+
14970
+ /**
14971
+ * Builds the default DOM `className`.
14972
+ *
14973
+ * @return {string}
14974
+ * The DOM `className` for this object.
14975
+ */
14976
+
14977
+
14978
+ FullscreenToggle.prototype.buildCSSClass = function buildCSSClass() {
14979
+ return 'vjs-fullscreen-control ' + _Button.prototype.buildCSSClass.call(this);
14980
+ };
14981
+
14982
+ /**
14983
+ * Handles fullscreenchange on the player and change control text accordingly.
14984
+ *
14985
+ * @param {EventTarget~Event} [event]
14986
+ * The {@link Player#fullscreenchange} event that caused this function to be
14987
+ * called.
14988
+ *
14989
+ * @listens Player#fullscreenchange
14990
+ */
14991
+
14992
+
14993
+ FullscreenToggle.prototype.handleFullscreenChange = function handleFullscreenChange(event) {
14994
+ if (this.player_.isFullscreen()) {
14995
+ this.controlText('Non-Fullscreen');
14996
+ } else {
14997
+ this.controlText('Fullscreen');
14998
+ }
14999
+ };
15000
+
15001
+ /**
15002
+ * This gets called when an `FullscreenToggle` is "clicked". See
15003
+ * {@link ClickableComponent} for more detailed information on what a click can be.
15004
+ *
15005
+ * @param {EventTarget~Event} [event]
15006
+ * The `keydown`, `tap`, or `click` event that caused this function to be
15007
+ * called.
15008
+ *
15009
+ * @listens tap
15010
+ * @listens click
15011
+ */
15012
+
15013
+
15014
+ FullscreenToggle.prototype.handleClick = function handleClick(event) {
15015
+ if (!this.player_.isFullscreen()) {
15016
+ this.player_.requestFullscreen();
15017
+ } else {
15018
+ this.player_.exitFullscreen();
15019
+ }
15020
+ };
15021
+
15022
+ return FullscreenToggle;
15023
+ }(Button);
15024
+
15025
+ /**
15026
+ * The text that should display over the `FullscreenToggle`s controls. Added for localization.
15027
+ *
15028
+ * @type {string}
15029
+ * @private
15030
+ */
15031
+
15032
+
15033
+ FullscreenToggle.prototype.controlText_ = 'Fullscreen';
15034
+
15035
+ Component.registerComponent('FullscreenToggle', FullscreenToggle);
15036
+
15037
+ /**
15038
+ * Check if volume control is supported and if it isn't hide the
15039
+ * `Component` that was passed using the `vjs-hidden` class.
15040
+ *
15041
+ * @param {Component} self
15042
+ * The component that should be hidden if volume is unsupported
15043
+ *
15044
+ * @param {Player} player
15045
+ * A reference to the player
15046
+ *
15047
+ * @private
15048
+ */
15049
+ var checkVolumeSupport = function checkVolumeSupport(self, player) {
15050
+ // hide volume controls when they're not supported by the current tech
15051
+ if (player.tech_ && !player.tech_.featuresVolumeControl) {
15052
+ self.addClass('vjs-hidden');
15053
+ }
15054
+
15055
+ self.on(player, 'loadstart', function () {
15056
+ if (!player.tech_.featuresVolumeControl) {
15057
+ self.addClass('vjs-hidden');
15058
+ } else {
15059
+ self.removeClass('vjs-hidden');
15060
+ }
15061
+ });
15062
+ };
15063
+
15064
+ /**
15065
+ * @file volume-level.js
15066
+ */
15067
+
15068
+ /**
15069
+ * Shows volume level
15070
+ *
15071
+ * @extends Component
15072
+ */
15073
+
15074
+ var VolumeLevel = function (_Component) {
15075
+ inherits(VolumeLevel, _Component);
15076
+
15077
+ function VolumeLevel() {
15078
+ classCallCheck(this, VolumeLevel);
15079
+ return possibleConstructorReturn(this, _Component.apply(this, arguments));
15080
+ }
15081
+
15082
+ /**
15083
+ * Create the `Component`'s DOM element
15084
+ *
15085
+ * @return {Element}
15086
+ * The element that was created.
15087
+ */
15088
+ VolumeLevel.prototype.createEl = function createEl() {
15089
+ return _Component.prototype.createEl.call(this, 'div', {
15090
+ className: 'vjs-volume-level',
15091
+ innerHTML: '<span class="vjs-control-text"></span>'
15092
+ });
15093
+ };
15094
+
15095
+ return VolumeLevel;
15096
+ }(Component);
15097
+
15098
+ Component.registerComponent('VolumeLevel', VolumeLevel);
15099
+
15100
+ /**
15101
+ * @file volume-bar.js
15102
+ */
15103
+
15104
+ /**
15105
+ * The bar that contains the volume level and can be clicked on to adjust the level
15106
+ *
15107
+ * @extends Slider
15108
+ */
15109
+
15110
+ var VolumeBar = function (_Slider) {
15111
+ inherits(VolumeBar, _Slider);
15112
+
15113
+ /**
15114
+ * Creates an instance of this class.
15115
+ *
15116
+ * @param {Player} player
15117
+ * The `Player` that this class should be attached to.
15118
+ *
15119
+ * @param {Object} [options]
15120
+ * The key/value store of player options.
15121
+ */
15122
+ function VolumeBar(player, options) {
15123
+ classCallCheck(this, VolumeBar);
15124
+
15125
+ var _this = possibleConstructorReturn(this, _Slider.call(this, player, options));
15126
+
15127
+ _this.on('slideractive', _this.updateLastVolume_);
15128
+ _this.on(player, 'volumechange', _this.updateARIAAttributes);
15129
+ player.ready(function () {
15130
+ return _this.updateARIAAttributes();
15131
+ });
15132
+ return _this;
15133
+ }
15134
+
15135
+ /**
15136
+ * Create the `Component`'s DOM element
15137
+ *
15138
+ * @return {Element}
15139
+ * The element that was created.
15140
+ */
15141
+
15142
+
15143
+ VolumeBar.prototype.createEl = function createEl$$1() {
15144
+ return _Slider.prototype.createEl.call(this, 'div', {
15145
+ className: 'vjs-volume-bar vjs-slider-bar'
15146
+ }, {
15147
+ 'aria-label': this.localize('Volume Level'),
15148
+ 'aria-live': 'polite'
15149
+ });
15150
+ };
15151
+
15152
+ /**
15153
+ * Handle mouse down on volume bar
15154
+ *
15155
+ * @param {EventTarget~Event} event
15156
+ * The `mousedown` event that caused this to run.
15157
+ *
15158
+ * @listens mousedown
15159
+ */
15160
+
15161
+
15162
+ VolumeBar.prototype.handleMouseDown = function handleMouseDown(event) {
15163
+ if (!isSingleLeftClick(event)) {
15164
+ return;
15165
+ }
15166
+
15167
+ _Slider.prototype.handleMouseDown.call(this, event);
15168
+ };
15169
+
15170
+ /**
15171
+ * Handle movement events on the {@link VolumeMenuButton}.
15172
+ *
15173
+ * @param {EventTarget~Event} event
15174
+ * The event that caused this function to run.
15175
+ *
15176
+ * @listens mousemove
15177
+ */
15178
+
15179
+
15180
+ VolumeBar.prototype.handleMouseMove = function handleMouseMove(event) {
15181
+ if (!isSingleLeftClick(event)) {
15182
+ return;
15183
+ }
15184
+
15185
+ this.checkMuted();
15186
+ this.player_.volume(this.calculateDistance(event));
15187
+ };
15188
+
15189
+ /**
15190
+ * If the player is muted unmute it.
15191
+ */
15192
+
15193
+
15194
+ VolumeBar.prototype.checkMuted = function checkMuted() {
15195
+ if (this.player_.muted()) {
15196
+ this.player_.muted(false);
15197
+ }
15198
+ };
15199
+
15200
+ /**
15201
+ * Get percent of volume level
15202
+ *
15203
+ * @return {number}
15204
+ * Volume level percent as a decimal number.
15205
+ */
15206
+
15207
+
15208
+ VolumeBar.prototype.getPercent = function getPercent() {
15209
+ if (this.player_.muted()) {
15210
+ return 0;
15211
+ }
15212
+ return this.player_.volume();
15213
+ };
15214
+
15215
+ /**
15216
+ * Increase volume level for keyboard users
15217
+ */
15218
+
15219
+
15220
+ VolumeBar.prototype.stepForward = function stepForward() {
15221
+ this.checkMuted();
15222
+ this.player_.volume(this.player_.volume() + 0.1);
15223
+ };
15224
+
15225
+ /**
15226
+ * Decrease volume level for keyboard users
15227
+ */
15228
+
15229
+
15230
+ VolumeBar.prototype.stepBack = function stepBack() {
15231
+ this.checkMuted();
15232
+ this.player_.volume(this.player_.volume() - 0.1);
15233
+ };
15234
+
15235
+ /**
15236
+ * Update ARIA accessibility attributes
15237
+ *
15238
+ * @param {EventTarget~Event} [event]
15239
+ * The `volumechange` event that caused this function to run.
15240
+ *
15241
+ * @listens Player#volumechange
15242
+ */
15243
+
15244
+
15245
+ VolumeBar.prototype.updateARIAAttributes = function updateARIAAttributes(event) {
15246
+ var ariaValue = this.player_.muted() ? 0 : this.volumeAsPercentage_();
15247
+
15248
+ this.el_.setAttribute('aria-valuenow', ariaValue);
15249
+ this.el_.setAttribute('aria-valuetext', ariaValue + '%');
15250
+ };
15251
+
15252
+ /**
15253
+ * Returns the current value of the player volume as a percentage
15254
+ *
15255
+ * @private
15256
+ */
15257
+
15258
+
15259
+ VolumeBar.prototype.volumeAsPercentage_ = function volumeAsPercentage_() {
15260
+ return Math.round(this.player_.volume() * 100);
15261
+ };
15262
+
15263
+ /**
15264
+ * When user starts dragging the VolumeBar, store the volume and listen for
15265
+ * the end of the drag. When the drag ends, if the volume was set to zero,
15266
+ * set lastVolume to the stored volume.
15267
+ *
15268
+ * @listens slideractive
15269
+ * @private
15270
+ */
15271
+
15272
+
15273
+ VolumeBar.prototype.updateLastVolume_ = function updateLastVolume_() {
15274
+ var _this2 = this;
15275
+
15276
+ var volumeBeforeDrag = this.player_.volume();
15277
+
15278
+ this.one('sliderinactive', function () {
15279
+ if (_this2.player_.volume() === 0) {
15280
+ _this2.player_.lastVolume_(volumeBeforeDrag);
15281
+ }
15282
+ });
15283
+ };
15284
+
15285
+ return VolumeBar;
15286
+ }(Slider);
15287
+
15288
+ /**
15289
+ * Default options for the `VolumeBar`
15290
+ *
15291
+ * @type {Object}
15292
+ * @private
15293
+ */
15294
+
15295
+
15296
+ VolumeBar.prototype.options_ = {
15297
+ children: ['volumeLevel'],
15298
+ barName: 'volumeLevel'
15299
+ };
15300
+
15301
+ /**
15302
+ * Call the update event for this Slider when this event happens on the player.
15303
+ *
15304
+ * @type {string}
15305
+ */
15306
+ VolumeBar.prototype.playerEvent = 'volumechange';
15307
+
15308
+ Component.registerComponent('VolumeBar', VolumeBar);
15309
+
15310
+ /**
15311
+ * @file volume-control.js
15312
+ */
15313
+
15314
+ /**
15315
+ * The component for controlling the volume level
15316
+ *
15317
+ * @extends Component
15318
+ */
15319
+
15320
+ var VolumeControl = function (_Component) {
15321
+ inherits(VolumeControl, _Component);
15322
+
15323
+ /**
15324
+ * Creates an instance of this class.
15325
+ *
15326
+ * @param {Player} player
15327
+ * The `Player` that this class should be attached to.
15328
+ *
15329
+ * @param {Object} [options={}]
15330
+ * The key/value store of player options.
15331
+ */
15332
+ function VolumeControl(player) {
15333
+ var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
15334
+ classCallCheck(this, VolumeControl);
15335
+
15336
+ options.vertical = options.vertical || false;
15337
+
15338
+ // Pass the vertical option down to the VolumeBar if
15339
+ // the VolumeBar is turned on.
15340
+ if (typeof options.volumeBar === 'undefined' || isPlain(options.volumeBar)) {
15341
+ options.volumeBar = options.volumeBar || {};
15342
+ options.volumeBar.vertical = options.vertical;
15343
+ }
15344
+
15345
+ // hide this control if volume support is missing
15346
+ var _this = possibleConstructorReturn(this, _Component.call(this, player, options));
15347
+
15348
+ checkVolumeSupport(_this, player);
15349
+
15350
+ _this.throttledHandleMouseMove = throttle(bind(_this, _this.handleMouseMove), 25);
15351
+
15352
+ _this.on('mousedown', _this.handleMouseDown);
15353
+ _this.on('touchstart', _this.handleMouseDown);
15354
+
15355
+ // while the slider is active (the mouse has been pressed down and
15356
+ // is dragging) or in focus we do not want to hide the VolumeBar
15357
+ _this.on(_this.volumeBar, ['focus', 'slideractive'], function () {
15358
+ _this.volumeBar.addClass('vjs-slider-active');
15359
+ _this.addClass('vjs-slider-active');
15360
+ _this.trigger('slideractive');
15361
+ });
15362
+
15363
+ _this.on(_this.volumeBar, ['blur', 'sliderinactive'], function () {
15364
+ _this.volumeBar.removeClass('vjs-slider-active');
15365
+ _this.removeClass('vjs-slider-active');
15366
+ _this.trigger('sliderinactive');
15367
+ });
15368
+ return _this;
15369
+ }
15370
+
15371
+ /**
15372
+ * Create the `Component`'s DOM element
15373
+ *
15374
+ * @return {Element}
15375
+ * The element that was created.
15376
+ */
15377
+
15378
+
15379
+ VolumeControl.prototype.createEl = function createEl() {
15380
+ var orientationClass = 'vjs-volume-horizontal';
15381
+
15382
+ if (this.options_.vertical) {
15383
+ orientationClass = 'vjs-volume-vertical';
15384
+ }
15385
+
15386
+ return _Component.prototype.createEl.call(this, 'div', {
15387
+ className: 'vjs-volume-control vjs-control ' + orientationClass
15388
+ });
15389
+ };
15390
+
15391
+ /**
15392
+ * Handle `mousedown` or `touchstart` events on the `VolumeControl`.
15393
+ *
15394
+ * @param {EventTarget~Event} event
15395
+ * `mousedown` or `touchstart` event that triggered this function
15396
+ *
15397
+ * @listens mousedown
15398
+ * @listens touchstart
15399
+ */
15400
+
15401
+
15402
+ VolumeControl.prototype.handleMouseDown = function handleMouseDown(event) {
15403
+ var doc = this.el_.ownerDocument;
15404
+
15405
+ this.on(doc, 'mousemove', this.throttledHandleMouseMove);
15406
+ this.on(doc, 'touchmove', this.throttledHandleMouseMove);
15407
+ this.on(doc, 'mouseup', this.handleMouseUp);
15408
+ this.on(doc, 'touchend', this.handleMouseUp);
15409
+ };
15410
+
15411
+ /**
15412
+ * Handle `mouseup` or `touchend` events on the `VolumeControl`.
15413
+ *
15414
+ * @param {EventTarget~Event} event
15415
+ * `mouseup` or `touchend` event that triggered this function.
15416
+ *
15417
+ * @listens touchend
15418
+ * @listens mouseup
15419
+ */
15420
+
15421
+
15422
+ VolumeControl.prototype.handleMouseUp = function handleMouseUp(event) {
15423
+ var doc = this.el_.ownerDocument;
15424
+
15425
+ this.off(doc, 'mousemove', this.throttledHandleMouseMove);
15426
+ this.off(doc, 'touchmove', this.throttledHandleMouseMove);
15427
+ this.off(doc, 'mouseup', this.handleMouseUp);
15428
+ this.off(doc, 'touchend', this.handleMouseUp);
15429
+ };
15430
+
15431
+ /**
15432
+ * Handle `mousedown` or `touchstart` events on the `VolumeControl`.
15433
+ *
15434
+ * @param {EventTarget~Event} event
15435
+ * `mousedown` or `touchstart` event that triggered this function
15436
+ *
15437
+ * @listens mousedown
15438
+ * @listens touchstart
15439
+ */
15440
+
15441
+
15442
+ VolumeControl.prototype.handleMouseMove = function handleMouseMove(event) {
15443
+ this.volumeBar.handleMouseMove(event);
15444
+ };
15445
+
15446
+ return VolumeControl;
15447
+ }(Component);
15448
+
15449
+ /**
15450
+ * Default options for the `VolumeControl`
15451
+ *
15452
+ * @type {Object}
15453
+ * @private
15454
+ */
15455
+
15456
+
15457
+ VolumeControl.prototype.options_ = {
15458
+ children: ['volumeBar']
15459
+ };
15460
+
15461
+ Component.registerComponent('VolumeControl', VolumeControl);
15462
+
15463
+ /**
15464
+ * Check if muting volume is supported and if it isn't hide the mute toggle
15465
+ * button.
15466
+ *
15467
+ * @param {Component} self
15468
+ * A reference to the mute toggle button
15469
+ *
15470
+ * @param {Player} player
15471
+ * A reference to the player
15472
+ *
15473
+ * @private
15474
+ */
15475
+ var checkMuteSupport = function checkMuteSupport(self, player) {
15476
+ // hide mute toggle button if it's not supported by the current tech
15477
+ if (player.tech_ && !player.tech_.featuresMuteControl) {
15478
+ self.addClass('vjs-hidden');
15479
+ }
15480
+
15481
+ self.on(player, 'loadstart', function () {
15482
+ if (!player.tech_.featuresMuteControl) {
15483
+ self.addClass('vjs-hidden');
15484
+ } else {
15485
+ self.removeClass('vjs-hidden');
15486
+ }
15487
+ });
15488
+ };
15489
+
15490
+ /**
15491
+ * @file mute-toggle.js
15492
+ */
15493
+
15494
+ /**
15495
+ * A button component for muting the audio.
15496
+ *
15497
+ * @extends Button
15498
+ */
15499
+
15500
+ var MuteToggle = function (_Button) {
15501
+ inherits(MuteToggle, _Button);
15502
+
15503
+ /**
15504
+ * Creates an instance of this class.
15505
+ *
15506
+ * @param {Player} player
15507
+ * The `Player` that this class should be attached to.
15508
+ *
15509
+ * @param {Object} [options]
15510
+ * The key/value store of player options.
15511
+ */
15512
+ function MuteToggle(player, options) {
15513
+ classCallCheck(this, MuteToggle);
15514
+
15515
+ // hide this control if volume support is missing
15516
+ var _this = possibleConstructorReturn(this, _Button.call(this, player, options));
15517
+
15518
+ checkMuteSupport(_this, player);
15519
+
15520
+ _this.on(player, ['loadstart', 'volumechange'], _this.update);
15521
+ return _this;
15522
+ }
15523
+
15524
+ /**
15525
+ * Builds the default DOM `className`.
15526
+ *
15527
+ * @return {string}
15528
+ * The DOM `className` for this object.
15529
+ */
15530
+
15531
+
15532
+ MuteToggle.prototype.buildCSSClass = function buildCSSClass() {
15533
+ return 'vjs-mute-control ' + _Button.prototype.buildCSSClass.call(this);
15534
+ };
15535
+
15536
+ /**
15537
+ * This gets called when an `MuteToggle` is "clicked". See
15538
+ * {@link ClickableComponent} for more detailed information on what a click can be.
15539
+ *
15540
+ * @param {EventTarget~Event} [event]
15541
+ * The `keydown`, `tap`, or `click` event that caused this function to be
15542
+ * called.
15543
+ *
15544
+ * @listens tap
15545
+ * @listens click
15546
+ */
15547
+
15548
+
15549
+ MuteToggle.prototype.handleClick = function handleClick(event) {
15550
+ var vol = this.player_.volume();
15551
+ var lastVolume = this.player_.lastVolume_();
15552
+
15553
+ if (vol === 0) {
15554
+ var volumeToSet = lastVolume < 0.1 ? 0.1 : lastVolume;
15555
+
15556
+ this.player_.volume(volumeToSet);
15557
+ this.player_.muted(false);
15558
+ } else {
15559
+ this.player_.muted(this.player_.muted() ? false : true);
15560
+ }
15561
+ };
15562
+
15563
+ /**
15564
+ * Update the `MuteToggle` button based on the state of `volume` and `muted`
15565
+ * on the player.
15566
+ *
15567
+ * @param {EventTarget~Event} [event]
15568
+ * The {@link Player#loadstart} event if this function was called
15569
+ * through an event.
15570
+ *
15571
+ * @listens Player#loadstart
15572
+ * @listens Player#volumechange
15573
+ */
15574
+
15575
+
15576
+ MuteToggle.prototype.update = function update(event) {
15577
+ this.updateIcon_();
15578
+ this.updateControlText_();
15579
+ };
15580
+
15581
+ /**
15582
+ * Update the appearance of the `MuteToggle` icon.
15583
+ *
15584
+ * Possible states (given `level` variable below):
15585
+ * - 0: crossed out
15586
+ * - 1: zero bars of volume
15587
+ * - 2: one bar of volume
15588
+ * - 3: two bars of volume
15589
+ *
15590
+ * @private
15591
+ */
15592
+
15593
+
15594
+ MuteToggle.prototype.updateIcon_ = function updateIcon_() {
15595
+ var vol = this.player_.volume();
15596
+ var level = 3;
15597
+
15598
+ // in iOS when a player is loaded with muted attribute
15599
+ // and volume is changed with a native mute button
15600
+ // we want to make sure muted state is updated
15601
+ if (IS_IOS) {
15602
+ this.player_.muted(this.player_.tech_.el_.muted);
15603
+ }
15604
+
15605
+ if (vol === 0 || this.player_.muted()) {
15606
+ level = 0;
15607
+ } else if (vol < 0.33) {
15608
+ level = 1;
15609
+ } else if (vol < 0.67) {
15610
+ level = 2;
15611
+ }
15612
+
15613
+ // TODO improve muted icon classes
15614
+ for (var i = 0; i < 4; i++) {
15615
+ removeClass(this.el_, 'vjs-vol-' + i);
15616
+ }
15617
+ addClass(this.el_, 'vjs-vol-' + level);
15618
+ };
15619
+
15620
+ /**
15621
+ * If `muted` has changed on the player, update the control text
15622
+ * (`title` attribute on `vjs-mute-control` element and content of
15623
+ * `vjs-control-text` element).
15624
+ *
15625
+ * @private
15626
+ */
15627
+
15628
+
15629
+ MuteToggle.prototype.updateControlText_ = function updateControlText_() {
15630
+ var soundOff = this.player_.muted() || this.player_.volume() === 0;
15631
+ var text = soundOff ? 'Unmute' : 'Mute';
15632
+
15633
+ if (this.controlText() !== text) {
15634
+ this.controlText(text);
15635
+ }
15636
+ };
15637
+
15638
+ return MuteToggle;
15639
+ }(Button);
15640
+
15641
+ /**
15642
+ * The text that should display over the `MuteToggle`s controls. Added for localization.
15643
+ *
15644
+ * @type {string}
15645
+ * @private
15646
+ */
15647
+
15648
+
15649
+ MuteToggle.prototype.controlText_ = 'Mute';
15650
+
15651
+ Component.registerComponent('MuteToggle', MuteToggle);
15652
+
15653
+ /**
15654
+ * @file volume-control.js
15655
+ */
15656
+
15657
+ /**
15658
+ * A Component to contain the MuteToggle and VolumeControl so that
15659
+ * they can work together.
15660
+ *
15661
+ * @extends Component
15662
+ */
15663
+
15664
+ var VolumePanel = function (_Component) {
15665
+ inherits(VolumePanel, _Component);
15666
+
15667
+ /**
15668
+ * Creates an instance of this class.
15669
+ *
15670
+ * @param {Player} player
15671
+ * The `Player` that this class should be attached to.
15672
+ *
15673
+ * @param {Object} [options={}]
15674
+ * The key/value store of player options.
15675
+ */
15676
+ function VolumePanel(player) {
15677
+ var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
15678
+ classCallCheck(this, VolumePanel);
15679
+
15680
+ if (typeof options.inline !== 'undefined') {
15681
+ options.inline = options.inline;
15682
+ } else {
15683
+ options.inline = true;
15684
+ }
15685
+
15686
+ // pass the inline option down to the VolumeControl as vertical if
15687
+ // the VolumeControl is on.
15688
+ if (typeof options.volumeControl === 'undefined' || isPlain(options.volumeControl)) {
15689
+ options.volumeControl = options.volumeControl || {};
15690
+ options.volumeControl.vertical = !options.inline;
15691
+ }
15692
+
15693
+ var _this = possibleConstructorReturn(this, _Component.call(this, player, options));
15694
+
15695
+ _this.on(player, ['loadstart'], _this.volumePanelState_);
15696
+
15697
+ // while the slider is active (the mouse has been pressed down and
15698
+ // is dragging) we do not want to hide the VolumeBar
15699
+ _this.on(_this.volumeControl, ['slideractive'], _this.sliderActive_);
15700
+
15701
+ _this.on(_this.volumeControl, ['sliderinactive'], _this.sliderInactive_);
15702
+ return _this;
15703
+ }
15704
+
15705
+ /**
15706
+ * Add vjs-slider-active class to the VolumePanel
15707
+ *
15708
+ * @listens VolumeControl#slideractive
15709
+ * @private
15710
+ */
15711
+
15712
+
15713
+ VolumePanel.prototype.sliderActive_ = function sliderActive_() {
15714
+ this.addClass('vjs-slider-active');
15715
+ };
15716
+
15717
+ /**
15718
+ * Removes vjs-slider-active class to the VolumePanel
15719
+ *
15720
+ * @listens VolumeControl#sliderinactive
15721
+ * @private
15722
+ */
15723
+
15724
+
15725
+ VolumePanel.prototype.sliderInactive_ = function sliderInactive_() {
15726
+ this.removeClass('vjs-slider-active');
15727
+ };
15728
+
15729
+ /**
15730
+ * Adds vjs-hidden or vjs-mute-toggle-only to the VolumePanel
15731
+ * depending on MuteToggle and VolumeControl state
15732
+ *
15733
+ * @listens Player#loadstart
15734
+ * @private
15735
+ */
15736
+
15737
+
15738
+ VolumePanel.prototype.volumePanelState_ = function volumePanelState_() {
15739
+ // hide volume panel if neither volume control or mute toggle
15740
+ // are displayed
15741
+ if (this.volumeControl.hasClass('vjs-hidden') && this.muteToggle.hasClass('vjs-hidden')) {
15742
+ this.addClass('vjs-hidden');
15743
+ }
15744
+
15745
+ // if only mute toggle is visible we don't want
15746
+ // volume panel expanding when hovered or active
15747
+ if (this.volumeControl.hasClass('vjs-hidden') && !this.muteToggle.hasClass('vjs-hidden')) {
15748
+ this.addClass('vjs-mute-toggle-only');
15749
+ }
15750
+ };
15751
+
15752
+ /**
15753
+ * Create the `Component`'s DOM element
15754
+ *
15755
+ * @return {Element}
15756
+ * The element that was created.
15757
+ */
15758
+
15759
+
15760
+ VolumePanel.prototype.createEl = function createEl() {
15761
+ var orientationClass = 'vjs-volume-panel-horizontal';
15762
+
15763
+ if (!this.options_.inline) {
15764
+ orientationClass = 'vjs-volume-panel-vertical';
15765
+ }
15766
+
15767
+ return _Component.prototype.createEl.call(this, 'div', {
15768
+ className: 'vjs-volume-panel vjs-control ' + orientationClass
15769
+ });
15770
+ };
15771
+
15772
+ return VolumePanel;
15773
+ }(Component);
15774
+
15775
+ /**
15776
+ * Default options for the `VolumeControl`
15777
+ *
15778
+ * @type {Object}
15779
+ * @private
15780
+ */
15781
+
15782
+
15783
+ VolumePanel.prototype.options_ = {
15784
+ children: ['muteToggle', 'volumeControl']
15785
+ };
15786
+
15787
+ Component.registerComponent('VolumePanel', VolumePanel);
15788
+
15789
+ /**
15790
+ * @file menu.js
15791
+ */
15792
+
15793
+ /**
15794
+ * The Menu component is used to build popup menus, including subtitle and
15795
+ * captions selection menus.
15796
+ *
15797
+ * @extends Component
15798
+ */
15799
+
15800
+ var Menu = function (_Component) {
15801
+ inherits(Menu, _Component);
15802
+
15803
+ /**
15804
+ * Create an instance of this class.
15805
+ *
15806
+ * @param {Player} player
15807
+ * the player that this component should attach to
15808
+ *
15809
+ * @param {Object} [options]
15810
+ * Object of option names and values
15811
+ *
15812
+ */
15813
+ function Menu(player, options) {
15814
+ classCallCheck(this, Menu);
15815
+
15816
+ var _this = possibleConstructorReturn(this, _Component.call(this, player, options));
15817
+
15818
+ if (options) {
15819
+ _this.menuButton_ = options.menuButton;
15820
+ }
15821
+
15822
+ _this.focusedChild_ = -1;
15823
+
15824
+ _this.on('keydown', _this.handleKeyPress);
15825
+ return _this;
15826
+ }
15827
+
15828
+ /**
15829
+ * Add a {@link MenuItem} to the menu.
15830
+ *
15831
+ * @param {Object|string} component
15832
+ * The name or instance of the `MenuItem` to add.
15833
+ *
15834
+ */
15835
+
15836
+
15837
+ Menu.prototype.addItem = function addItem(component) {
15838
+ this.addChild(component);
15839
+ component.on('click', bind(this, function (event) {
15840
+ // Unpress the associated MenuButton, and move focus back to it
15841
+ if (this.menuButton_) {
15842
+ this.menuButton_.unpressButton();
15843
+
15844
+ // don't focus menu button if item is a caption settings item
15845
+ // because focus will move elsewhere
15846
+ if (component.name() !== 'CaptionSettingsMenuItem') {
15847
+ this.menuButton_.focus();
15848
+ }
15849
+ }
15850
+ }));
15851
+ };
15852
+
15853
+ /**
15854
+ * Create the `Menu`s DOM element.
15855
+ *
15856
+ * @return {Element}
15857
+ * the element that was created
15858
+ */
15859
+
15860
+
15861
+ Menu.prototype.createEl = function createEl$$1() {
15862
+ var contentElType = this.options_.contentElType || 'ul';
15863
+
15864
+ this.contentEl_ = createEl(contentElType, {
15865
+ className: 'vjs-menu-content'
15866
+ });
15867
+
15868
+ this.contentEl_.setAttribute('role', 'menu');
15869
+
15870
+ var el = _Component.prototype.createEl.call(this, 'div', {
15871
+ append: this.contentEl_,
15872
+ className: 'vjs-menu'
15873
+ });
15874
+
15875
+ el.appendChild(this.contentEl_);
15876
+
15877
+ // Prevent clicks from bubbling up. Needed for Menu Buttons,
15878
+ // where a click on the parent is significant
15879
+ on(el, 'click', function (event) {
15880
+ event.preventDefault();
15881
+ event.stopImmediatePropagation();
15882
+ });
15883
+
15884
+ return el;
15885
+ };
15886
+
15887
+ Menu.prototype.dispose = function dispose() {
15888
+ this.contentEl_ = null;
15889
+
15890
+ _Component.prototype.dispose.call(this);
15891
+ };
15892
+
15893
+ /**
15894
+ * Handle a `keydown` event on this menu. This listener is added in the constructor.
15895
+ *
15896
+ * @param {EventTarget~Event} event
15897
+ * A `keydown` event that happened on the menu.
15898
+ *
15899
+ * @listens keydown
15900
+ */
15901
+
15902
+
15903
+ Menu.prototype.handleKeyPress = function handleKeyPress(event) {
15904
+ // Left and Down Arrows
15905
+ if (event.which === 37 || event.which === 40) {
15906
+ event.preventDefault();
15907
+ this.stepForward();
15908
+
15909
+ // Up and Right Arrows
15910
+ } else if (event.which === 38 || event.which === 39) {
15911
+ event.preventDefault();
15912
+ this.stepBack();
15913
+ }
15914
+ };
15915
+
15916
+ /**
15917
+ * Move to next (lower) menu item for keyboard users.
15918
+ */
15919
+
15920
+
15921
+ Menu.prototype.stepForward = function stepForward() {
15922
+ var stepChild = 0;
15923
+
15924
+ if (this.focusedChild_ !== undefined) {
15925
+ stepChild = this.focusedChild_ + 1;
15926
+ }
15927
+ this.focus(stepChild);
15928
+ };
15929
+
15930
+ /**
15931
+ * Move to previous (higher) menu item for keyboard users.
15932
+ */
15933
+
15934
+
15935
+ Menu.prototype.stepBack = function stepBack() {
15936
+ var stepChild = 0;
15937
+
15938
+ if (this.focusedChild_ !== undefined) {
15939
+ stepChild = this.focusedChild_ - 1;
15940
+ }
15941
+ this.focus(stepChild);
15942
+ };
15943
+
15944
+ /**
15945
+ * Set focus on a {@link MenuItem} in the `Menu`.
15946
+ *
15947
+ * @param {Object|string} [item=0]
15948
+ * Index of child item set focus on.
15949
+ */
15950
+
15951
+
15952
+ Menu.prototype.focus = function focus() {
15953
+ var item = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0;
15954
+
15955
+ var children = this.children().slice();
15956
+ var haveTitle = children.length && children[0].className && /vjs-menu-title/.test(children[0].className);
15957
+
15958
+ if (haveTitle) {
15959
+ children.shift();
15960
+ }
15961
+
15962
+ if (children.length > 0) {
15963
+ if (item < 0) {
15964
+ item = 0;
15965
+ } else if (item >= children.length) {
15966
+ item = children.length - 1;
15967
+ }
15968
+
15969
+ this.focusedChild_ = item;
15970
+
15971
+ children[item].el_.focus();
15972
+ }
15973
+ };
15974
+
15975
+ return Menu;
15976
+ }(Component);
15977
+
15978
+ Component.registerComponent('Menu', Menu);
15979
+
15980
+ /**
15981
+ * @file menu-button.js
15982
+ */
15983
+
15984
+ /**
15985
+ * A `MenuButton` class for any popup {@link Menu}.
15986
+ *
15987
+ * @extends Component
15988
+ */
15989
+
15990
+ var MenuButton = function (_Component) {
15991
+ inherits(MenuButton, _Component);
15992
+
15993
+ /**
15994
+ * Creates an instance of this class.
15995
+ *
15996
+ * @param {Player} player
15997
+ * The `Player` that this class should be attached to.
15998
+ *
15999
+ * @param {Object} [options={}]
16000
+ * The key/value store of player options.
16001
+ */
16002
+ function MenuButton(player) {
16003
+ var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
16004
+ classCallCheck(this, MenuButton);
16005
+
16006
+ var _this = possibleConstructorReturn(this, _Component.call(this, player, options));
16007
+
16008
+ _this.menuButton_ = new Button(player, options);
16009
+
16010
+ _this.menuButton_.controlText(_this.controlText_);
16011
+ _this.menuButton_.el_.setAttribute('aria-haspopup', 'true');
16012
+
16013
+ // Add buildCSSClass values to the button, not the wrapper
16014
+ var buttonClass = Button.prototype.buildCSSClass();
16015
+
16016
+ _this.menuButton_.el_.className = _this.buildCSSClass() + ' ' + buttonClass;
16017
+ _this.menuButton_.removeClass('vjs-control');
16018
+
16019
+ _this.addChild(_this.menuButton_);
16020
+
16021
+ _this.update();
16022
+
16023
+ _this.enabled_ = true;
16024
+
16025
+ _this.on(_this.menuButton_, 'tap', _this.handleClick);
16026
+ _this.on(_this.menuButton_, 'click', _this.handleClick);
16027
+ _this.on(_this.menuButton_, 'focus', _this.handleFocus);
16028
+ _this.on(_this.menuButton_, 'blur', _this.handleBlur);
16029
+
16030
+ _this.on('keydown', _this.handleSubmenuKeyPress);
16031
+ return _this;
16032
+ }
16033
+
16034
+ /**
16035
+ * Update the menu based on the current state of its items.
16036
+ */
16037
+
16038
+
16039
+ MenuButton.prototype.update = function update() {
16040
+ var menu = this.createMenu();
16041
+
16042
+ if (this.menu) {
16043
+ this.menu.dispose();
16044
+ this.removeChild(this.menu);
16045
+ }
16046
+
16047
+ this.menu = menu;
16048
+ this.addChild(menu);
16049
+
16050
+ /**
16051
+ * Track the state of the menu button
16052
+ *
16053
+ * @type {Boolean}
16054
+ * @private
16055
+ */
16056
+ this.buttonPressed_ = false;
16057
+ this.menuButton_.el_.setAttribute('aria-expanded', 'false');
16058
+
16059
+ if (this.items && this.items.length <= this.hideThreshold_) {
16060
+ this.hide();
16061
+ } else {
16062
+ this.show();
16063
+ }
16064
+ };
16065
+
16066
+ /**
16067
+ * Create the menu and add all items to it.
16068
+ *
16069
+ * @return {Menu}
16070
+ * The constructed menu
16071
+ */
16072
+
16073
+
16074
+ MenuButton.prototype.createMenu = function createMenu() {
16075
+ var menu = new Menu(this.player_, { menuButton: this });
16076
+
16077
+ /**
16078
+ * Hide the menu if the number of items is less than or equal to this threshold. This defaults
16079
+ * to 0 and whenever we add items which can be hidden to the menu we'll increment it. We list
16080
+ * it here because every time we run `createMenu` we need to reset the value.
16081
+ *
16082
+ * @protected
16083
+ * @type {Number}
16084
+ */
16085
+ this.hideThreshold_ = 0;
16086
+
16087
+ // Add a title list item to the top
16088
+ if (this.options_.title) {
16089
+ var title = createEl('li', {
16090
+ className: 'vjs-menu-title',
16091
+ innerHTML: toTitleCase(this.options_.title),
16092
+ tabIndex: -1
16093
+ });
16094
+
16095
+ this.hideThreshold_ += 1;
16096
+
16097
+ menu.children_.unshift(title);
16098
+ prependTo(title, menu.contentEl());
16099
+ }
16100
+
16101
+ this.items = this.createItems();
16102
+
16103
+ if (this.items) {
16104
+ // Add menu items to the menu
16105
+ for (var i = 0; i < this.items.length; i++) {
16106
+ menu.addItem(this.items[i]);
16107
+ }
16108
+ }
16109
+
16110
+ return menu;
16111
+ };
16112
+
16113
+ /**
16114
+ * Create the list of menu items. Specific to each subclass.
16115
+ *
16116
+ * @abstract
16117
+ */
16118
+
16119
+
16120
+ MenuButton.prototype.createItems = function createItems() {};
16121
+
16122
+ /**
16123
+ * Create the `MenuButtons`s DOM element.
16124
+ *
16125
+ * @return {Element}
16126
+ * The element that gets created.
16127
+ */
16128
+
16129
+
16130
+ MenuButton.prototype.createEl = function createEl$$1() {
16131
+ return _Component.prototype.createEl.call(this, 'div', {
16132
+ className: this.buildWrapperCSSClass()
16133
+ }, {});
16134
+ };
16135
+
16136
+ /**
16137
+ * Allow sub components to stack CSS class names for the wrapper element
16138
+ *
16139
+ * @return {string}
16140
+ * The constructed wrapper DOM `className`
16141
+ */
16142
+
16143
+
16144
+ MenuButton.prototype.buildWrapperCSSClass = function buildWrapperCSSClass() {
16145
+ var menuButtonClass = 'vjs-menu-button';
16146
+
16147
+ // If the inline option is passed, we want to use different styles altogether.
16148
+ if (this.options_.inline === true) {
16149
+ menuButtonClass += '-inline';
16150
+ } else {
16151
+ menuButtonClass += '-popup';
16152
+ }
16153
+
16154
+ // TODO: Fix the CSS so that this isn't necessary
16155
+ var buttonClass = Button.prototype.buildCSSClass();
16156
+
16157
+ return 'vjs-menu-button ' + menuButtonClass + ' ' + buttonClass + ' ' + _Component.prototype.buildCSSClass.call(this);
16158
+ };
16159
+
16160
+ /**
16161
+ * Builds the default DOM `className`.
16162
+ *
16163
+ * @return {string}
16164
+ * The DOM `className` for this object.
16165
+ */
16166
+
16167
+
16168
+ MenuButton.prototype.buildCSSClass = function buildCSSClass() {
16169
+ var menuButtonClass = 'vjs-menu-button';
16170
+
16171
+ // If the inline option is passed, we want to use different styles altogether.
16172
+ if (this.options_.inline === true) {
16173
+ menuButtonClass += '-inline';
16174
+ } else {
16175
+ menuButtonClass += '-popup';
16176
+ }
16177
+
16178
+ return 'vjs-menu-button ' + menuButtonClass + ' ' + _Component.prototype.buildCSSClass.call(this);
16179
+ };
16180
+
16181
+ /**
16182
+ * Get or set the localized control text that will be used for accessibility.
16183
+ *
16184
+ * > NOTE: This will come from the internal `menuButton_` element.
16185
+ *
16186
+ * @param {string} [text]
16187
+ * Control text for element.
16188
+ *
16189
+ * @param {Element} [el=this.menuButton_.el()]
16190
+ * Element to set the title on.
16191
+ *
16192
+ * @return {string}
16193
+ * - The control text when getting
16194
+ */
16195
+
16196
+
16197
+ MenuButton.prototype.controlText = function controlText(text) {
16198
+ var el = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : this.menuButton_.el();
16199
+
16200
+ return this.menuButton_.controlText(text, el);
16201
+ };
16202
+
16203
+ /**
16204
+ * Handle a click on a `MenuButton`.
16205
+ * See {@link ClickableComponent#handleClick} for instances where this is called.
16206
+ *
16207
+ * @param {EventTarget~Event} event
16208
+ * The `keydown`, `tap`, or `click` event that caused this function to be
16209
+ * called.
16210
+ *
16211
+ * @listens tap
16212
+ * @listens click
16213
+ */
16214
+
16215
+
16216
+ MenuButton.prototype.handleClick = function handleClick(event) {
16217
+ // When you click the button it adds focus, which will show the menu.
16218
+ // So we'll remove focus when the mouse leaves the button. Focus is needed
16219
+ // for tab navigation.
16220
+
16221
+ this.one(this.menu.contentEl(), 'mouseleave', bind(this, function (e) {
16222
+ this.unpressButton();
16223
+ this.el_.blur();
16224
+ }));
16225
+ if (this.buttonPressed_) {
16226
+ this.unpressButton();
16227
+ } else {
16228
+ this.pressButton();
16229
+ }
16230
+ };
16231
+
16232
+ /**
16233
+ * Set the focus to the actual button, not to this element
16234
+ */
16235
+
16236
+
16237
+ MenuButton.prototype.focus = function focus() {
16238
+ this.menuButton_.focus();
16239
+ };
16240
+
16241
+ /**
16242
+ * Remove the focus from the actual button, not this element
16243
+ */
16244
+
16245
+
16246
+ MenuButton.prototype.blur = function blur() {
16247
+ this.menuButton_.blur();
16248
+ };
16249
+
16250
+ /**
16251
+ * This gets called when a `MenuButton` gains focus via a `focus` event.
16252
+ * Turns on listening for `keydown` events. When they happen it
16253
+ * calls `this.handleKeyPress`.
16254
+ *
16255
+ * @param {EventTarget~Event} event
16256
+ * The `focus` event that caused this function to be called.
16257
+ *
16258
+ * @listens focus
16259
+ */
16260
+
16261
+
16262
+ MenuButton.prototype.handleFocus = function handleFocus() {
16263
+ on(document_1, 'keydown', bind(this, this.handleKeyPress));
16264
+ };
16265
+
16266
+ /**
16267
+ * Called when a `MenuButton` loses focus. Turns off the listener for
16268
+ * `keydown` events. Which Stops `this.handleKeyPress` from getting called.
16269
+ *
16270
+ * @param {EventTarget~Event} event
16271
+ * The `blur` event that caused this function to be called.
16272
+ *
16273
+ * @listens blur
16274
+ */
16275
+
16276
+
16277
+ MenuButton.prototype.handleBlur = function handleBlur() {
16278
+ off(document_1, 'keydown', bind(this, this.handleKeyPress));
16279
+ };
16280
+
16281
+ /**
16282
+ * Handle tab, escape, down arrow, and up arrow keys for `MenuButton`. See
16283
+ * {@link ClickableComponent#handleKeyPress} for instances where this is called.
16284
+ *
16285
+ * @param {EventTarget~Event} event
16286
+ * The `keydown` event that caused this function to be called.
16287
+ *
16288
+ * @listens keydown
16289
+ */
16290
+
16291
+
16292
+ MenuButton.prototype.handleKeyPress = function handleKeyPress(event) {
16293
+
16294
+ // Escape (27) key or Tab (9) key unpress the 'button'
16295
+ if (event.which === 27 || event.which === 9) {
16296
+ if (this.buttonPressed_) {
16297
+ this.unpressButton();
16298
+ }
16299
+ // Don't preventDefault for Tab key - we still want to lose focus
16300
+ if (event.which !== 9) {
16301
+ event.preventDefault();
16302
+ // Set focus back to the menu button's button
16303
+ this.menuButton_.el_.focus();
16304
+ }
16305
+ // Up (38) key or Down (40) key press the 'button'
16306
+ } else if (event.which === 38 || event.which === 40) {
16307
+ if (!this.buttonPressed_) {
16308
+ this.pressButton();
16309
+ event.preventDefault();
16310
+ }
16311
+ }
16312
+ };
16313
+
16314
+ /**
16315
+ * Handle a `keydown` event on a sub-menu. The listener for this is added in
16316
+ * the constructor.
16317
+ *
16318
+ * @param {EventTarget~Event} event
16319
+ * Key press event
16320
+ *
16321
+ * @listens keydown
16322
+ */
16323
+
16324
+
16325
+ MenuButton.prototype.handleSubmenuKeyPress = function handleSubmenuKeyPress(event) {
16326
+
16327
+ // Escape (27) key or Tab (9) key unpress the 'button'
16328
+ if (event.which === 27 || event.which === 9) {
16329
+ if (this.buttonPressed_) {
16330
+ this.unpressButton();
16331
+ }
16332
+ // Don't preventDefault for Tab key - we still want to lose focus
16333
+ if (event.which !== 9) {
16334
+ event.preventDefault();
16335
+ // Set focus back to the menu button's button
16336
+ this.menuButton_.el_.focus();
16337
+ }
16338
+ }
16339
+ };
16340
+
16341
+ /**
16342
+ * Put the current `MenuButton` into a pressed state.
16343
+ */
16344
+
16345
+
16346
+ MenuButton.prototype.pressButton = function pressButton() {
16347
+ if (this.enabled_) {
16348
+ this.buttonPressed_ = true;
16349
+ this.menu.lockShowing();
16350
+ this.menuButton_.el_.setAttribute('aria-expanded', 'true');
16351
+
16352
+ // set the focus into the submenu, except on iOS where it is resulting in
16353
+ // undesired scrolling behavior when the player is in an iframe
16354
+ if (IS_IOS && isInFrame()) {
16355
+ // Return early so that the menu isn't focused
16356
+ return;
16357
+ }
16358
+
16359
+ this.menu.focus();
16360
+ }
16361
+ };
16362
+
16363
+ /**
16364
+ * Take the current `MenuButton` out of a pressed state.
16365
+ */
16366
+
16367
+
16368
+ MenuButton.prototype.unpressButton = function unpressButton() {
16369
+ if (this.enabled_) {
16370
+ this.buttonPressed_ = false;
16371
+ this.menu.unlockShowing();
16372
+ this.menuButton_.el_.setAttribute('aria-expanded', 'false');
16373
+ }
16374
+ };
16375
+
16376
+ /**
16377
+ * Disable the `MenuButton`. Don't allow it to be clicked.
16378
+ */
16379
+
16380
+
16381
+ MenuButton.prototype.disable = function disable() {
16382
+ this.unpressButton();
16383
+
16384
+ this.enabled_ = false;
16385
+ this.addClass('vjs-disabled');
16386
+
16387
+ this.menuButton_.disable();
16388
+ };
16389
+
16390
+ /**
16391
+ * Enable the `MenuButton`. Allow it to be clicked.
16392
+ */
16393
+
16394
+
16395
+ MenuButton.prototype.enable = function enable() {
16396
+ this.enabled_ = true;
16397
+ this.removeClass('vjs-disabled');
16398
+
16399
+ this.menuButton_.enable();
16400
+ };
16401
+
16402
+ return MenuButton;
16403
+ }(Component);
16404
+
16405
+ Component.registerComponent('MenuButton', MenuButton);
16406
+
16407
+ /**
16408
+ * @file track-button.js
16409
+ */
16410
+
16411
+ /**
16412
+ * The base class for buttons that toggle specific track types (e.g. subtitles).
16413
+ *
16414
+ * @extends MenuButton
16415
+ */
16416
+
16417
+ var TrackButton = function (_MenuButton) {
16418
+ inherits(TrackButton, _MenuButton);
16419
+
16420
+ /**
16421
+ * Creates an instance of this class.
16422
+ *
16423
+ * @param {Player} player
16424
+ * The `Player` that this class should be attached to.
16425
+ *
16426
+ * @param {Object} [options]
16427
+ * The key/value store of player options.
16428
+ */
16429
+ function TrackButton(player, options) {
16430
+ classCallCheck(this, TrackButton);
16431
+
16432
+ var tracks = options.tracks;
16433
+
16434
+ var _this = possibleConstructorReturn(this, _MenuButton.call(this, player, options));
16435
+
16436
+ if (_this.items.length <= 1) {
16437
+ _this.hide();
16438
+ }
16439
+
16440
+ if (!tracks) {
16441
+ return possibleConstructorReturn(_this);
16442
+ }
16443
+
16444
+ var updateHandler = bind(_this, _this.update);
16445
+
16446
+ tracks.addEventListener('removetrack', updateHandler);
16447
+ tracks.addEventListener('addtrack', updateHandler);
16448
+ _this.player_.on('ready', updateHandler);
16449
+
16450
+ _this.player_.on('dispose', function () {
16451
+ tracks.removeEventListener('removetrack', updateHandler);
16452
+ tracks.removeEventListener('addtrack', updateHandler);
16453
+ });
16454
+ return _this;
16455
+ }
16456
+
16457
+ return TrackButton;
16458
+ }(MenuButton);
16459
+
16460
+ Component.registerComponent('TrackButton', TrackButton);
16461
+
16462
+ /**
16463
+ * @file menu-item.js
16464
+ */
16465
+
16466
+ /**
16467
+ * The component for a menu item. `<li>`
16468
+ *
16469
+ * @extends ClickableComponent
16470
+ */
16471
+
16472
+ var MenuItem = function (_ClickableComponent) {
16473
+ inherits(MenuItem, _ClickableComponent);
16474
+
16475
+ /**
16476
+ * Creates an instance of the this class.
16477
+ *
16478
+ * @param {Player} player
16479
+ * The `Player` that this class should be attached to.
16480
+ *
16481
+ * @param {Object} [options={}]
16482
+ * The key/value store of player options.
16483
+ *
16484
+ */
16485
+ function MenuItem(player, options) {
16486
+ classCallCheck(this, MenuItem);
16487
+
16488
+ var _this = possibleConstructorReturn(this, _ClickableComponent.call(this, player, options));
16489
+
16490
+ _this.selectable = options.selectable;
16491
+ _this.isSelected_ = options.selected || false;
16492
+ _this.multiSelectable = options.multiSelectable;
16493
+
16494
+ _this.selected(_this.isSelected_);
16495
+
16496
+ if (_this.selectable) {
16497
+ if (_this.multiSelectable) {
16498
+ _this.el_.setAttribute('role', 'menuitemcheckbox');
16499
+ } else {
16500
+ _this.el_.setAttribute('role', 'menuitemradio');
16501
+ }
16502
+ } else {
16503
+ _this.el_.setAttribute('role', 'menuitem');
16504
+ }
16505
+ return _this;
16506
+ }
16507
+
16508
+ /**
16509
+ * Create the `MenuItem's DOM element
16510
+ *
16511
+ * @param {string} [type=li]
16512
+ * Element's node type, not actually used, always set to `li`.
16513
+ *
16514
+ * @param {Object} [props={}]
16515
+ * An object of properties that should be set on the element
16516
+ *
16517
+ * @param {Object} [attrs={}]
16518
+ * An object of attributes that should be set on the element
16519
+ *
16520
+ * @return {Element}
16521
+ * The element that gets created.
16522
+ */
16523
+
16524
+
16525
+ MenuItem.prototype.createEl = function createEl(type, props, attrs) {
16526
+ // The control is textual, not just an icon
16527
+ this.nonIconControl = true;
16528
+
16529
+ return _ClickableComponent.prototype.createEl.call(this, 'li', assign({
16530
+ className: 'vjs-menu-item',
16531
+ innerHTML: '<span class="vjs-menu-item-text">' + this.localize(this.options_.label) + '</span>',
16532
+ tabIndex: -1
16533
+ }, props), attrs);
16534
+ };
16535
+
16536
+ /**
16537
+ * Any click on a `MenuItem` puts it into the selected state.
16538
+ * See {@link ClickableComponent#handleClick} for instances where this is called.
16539
+ *
16540
+ * @param {EventTarget~Event} event
16541
+ * The `keydown`, `tap`, or `click` event that caused this function to be
16542
+ * called.
16543
+ *
16544
+ * @listens tap
16545
+ * @listens click
16546
+ */
16547
+
16548
+
16549
+ MenuItem.prototype.handleClick = function handleClick(event) {
16550
+ this.selected(true);
16551
+ };
16552
+
16553
+ /**
16554
+ * Set the state for this menu item as selected or not.
16555
+ *
16556
+ * @param {boolean} selected
16557
+ * if the menu item is selected or not
16558
+ */
16559
+
16560
+
16561
+ MenuItem.prototype.selected = function selected(_selected) {
16562
+ if (this.selectable) {
16563
+ if (_selected) {
16564
+ this.addClass('vjs-selected');
16565
+ this.el_.setAttribute('aria-checked', 'true');
16566
+ // aria-checked isn't fully supported by browsers/screen readers,
16567
+ // so indicate selected state to screen reader in the control text.
16568
+ this.controlText(', selected');
16569
+ this.isSelected_ = true;
16570
+ } else {
16571
+ this.removeClass('vjs-selected');
16572
+ this.el_.setAttribute('aria-checked', 'false');
16573
+ // Indicate un-selected state to screen reader
16574
+ this.controlText('');
16575
+ this.isSelected_ = false;
16576
+ }
16577
+ }
16578
+ };
16579
+
16580
+ return MenuItem;
16581
+ }(ClickableComponent);
16582
+
16583
+ Component.registerComponent('MenuItem', MenuItem);
16584
+
16585
+ /**
16586
+ * @file text-track-menu-item.js
16587
+ */
16588
+
16589
+ /**
16590
+ * The specific menu item type for selecting a language within a text track kind
16591
+ *
16592
+ * @extends MenuItem
16593
+ */
16594
+
16595
+ var TextTrackMenuItem = function (_MenuItem) {
16596
+ inherits(TextTrackMenuItem, _MenuItem);
16597
+
16598
+ /**
16599
+ * Creates an instance of this class.
16600
+ *
16601
+ * @param {Player} player
16602
+ * The `Player` that this class should be attached to.
16603
+ *
16604
+ * @param {Object} [options]
16605
+ * The key/value store of player options.
16606
+ */
16607
+ function TextTrackMenuItem(player, options) {
16608
+ classCallCheck(this, TextTrackMenuItem);
16609
+
16610
+ var track = options.track;
16611
+ var tracks = player.textTracks();
16612
+
16613
+ // Modify options for parent MenuItem class's init.
16614
+ options.label = track.label || track.language || 'Unknown';
16615
+ options.selected = track.mode === 'showing';
16616
+
16617
+ var _this = possibleConstructorReturn(this, _MenuItem.call(this, player, options));
16618
+
16619
+ _this.track = track;
16620
+ var changeHandler = function changeHandler() {
16621
+ for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
16622
+ args[_key] = arguments[_key];
16623
+ }
16624
+
16625
+ _this.handleTracksChange.apply(_this, args);
16626
+ };
16627
+ var selectedLanguageChangeHandler = function selectedLanguageChangeHandler() {
16628
+ for (var _len2 = arguments.length, args = Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
16629
+ args[_key2] = arguments[_key2];
16630
+ }
16631
+
16632
+ _this.handleSelectedLanguageChange.apply(_this, args);
16633
+ };
16634
+
16635
+ player.on(['loadstart', 'texttrackchange'], changeHandler);
16636
+ tracks.addEventListener('change', changeHandler);
16637
+ tracks.addEventListener('selectedlanguagechange', selectedLanguageChangeHandler);
16638
+ _this.on('dispose', function () {
16639
+ player.off(['loadstart', 'texttrackchange'], changeHandler);
16640
+ tracks.removeEventListener('change', changeHandler);
16641
+ tracks.removeEventListener('selectedlanguagechange', selectedLanguageChangeHandler);
16642
+ });
16643
+
16644
+ // iOS7 doesn't dispatch change events to TextTrackLists when an
16645
+ // associated track's mode changes. Without something like
16646
+ // Object.observe() (also not present on iOS7), it's not
16647
+ // possible to detect changes to the mode attribute and polyfill
16648
+ // the change event. As a poor substitute, we manually dispatch
16649
+ // change events whenever the controls modify the mode.
16650
+ if (tracks.onchange === undefined) {
16651
+ var event = void 0;
16652
+
16653
+ _this.on(['tap', 'click'], function () {
16654
+ if (_typeof(window_1.Event) !== 'object') {
16655
+ // Android 2.3 throws an Illegal Constructor error for window.Event
16656
+ try {
16657
+ event = new window_1.Event('change');
16658
+ } catch (err) {
16659
+ // continue regardless of error
16660
+ }
16661
+ }
16662
+
16663
+ if (!event) {
16664
+ event = document_1.createEvent('Event');
16665
+ event.initEvent('change', true, true);
16666
+ }
16667
+
16668
+ tracks.dispatchEvent(event);
16669
+ });
16670
+ }
16671
+
16672
+ // set the default state based on current tracks
16673
+ _this.handleTracksChange();
16674
+ return _this;
16675
+ }
16676
+
16677
+ /**
16678
+ * This gets called when an `TextTrackMenuItem` is "clicked". See
16679
+ * {@link ClickableComponent} for more detailed information on what a click can be.
16680
+ *
16681
+ * @param {EventTarget~Event} event
16682
+ * The `keydown`, `tap`, or `click` event that caused this function to be
16683
+ * called.
16684
+ *
16685
+ * @listens tap
16686
+ * @listens click
16687
+ */
16688
+
16689
+
16690
+ TextTrackMenuItem.prototype.handleClick = function handleClick(event) {
16691
+ var kind = this.track.kind;
16692
+ var kinds = this.track.kinds;
16693
+ var tracks = this.player_.textTracks();
16694
+
16695
+ if (!kinds) {
16696
+ kinds = [kind];
16697
+ }
16698
+
16699
+ _MenuItem.prototype.handleClick.call(this, event);
16700
+
16701
+ if (!tracks) {
16702
+ return;
16703
+ }
16704
+
16705
+ for (var i = 0; i < tracks.length; i++) {
16706
+ var track = tracks[i];
16707
+
16708
+ if (track === this.track && kinds.indexOf(track.kind) > -1) {
16709
+ if (track.mode !== 'showing') {
16710
+ track.mode = 'showing';
16711
+ }
16712
+ } else if (track.mode !== 'disabled') {
16713
+ track.mode = 'disabled';
16714
+ }
16715
+ }
16716
+ };
16717
+
16718
+ /**
16719
+ * Handle text track list change
16720
+ *
16721
+ * @param {EventTarget~Event} event
16722
+ * The `change` event that caused this function to be called.
16723
+ *
16724
+ * @listens TextTrackList#change
16725
+ */
16726
+
16727
+
16728
+ TextTrackMenuItem.prototype.handleTracksChange = function handleTracksChange(event) {
16729
+ var shouldBeSelected = this.track.mode === 'showing';
16730
+
16731
+ // Prevent redundant selected() calls because they may cause
16732
+ // screen readers to read the appended control text unnecessarily
16733
+ if (shouldBeSelected !== this.isSelected_) {
16734
+ this.selected(shouldBeSelected);
16735
+ }
16736
+ };
16737
+
16738
+ TextTrackMenuItem.prototype.handleSelectedLanguageChange = function handleSelectedLanguageChange(event) {
16739
+ if (this.track.mode === 'showing') {
16740
+ var selectedLanguage = this.player_.cache_.selectedLanguage;
16741
+
16742
+ // Don't replace the kind of track across the same language
16743
+ if (selectedLanguage && selectedLanguage.enabled && selectedLanguage.language === this.track.language && selectedLanguage.kind !== this.track.kind) {
16744
+ return;
16745
+ }
16746
+
16747
+ this.player_.cache_.selectedLanguage = {
16748
+ enabled: true,
16749
+ language: this.track.language,
16750
+ kind: this.track.kind
16751
+ };
16752
+ }
16753
+ };
16754
+
16755
+ TextTrackMenuItem.prototype.dispose = function dispose() {
16756
+ // remove reference to track object on dispose
16757
+ this.track = null;
16758
+
16759
+ _MenuItem.prototype.dispose.call(this);
16760
+ };
16761
+
16762
+ return TextTrackMenuItem;
16763
+ }(MenuItem);
16764
+
16765
+ Component.registerComponent('TextTrackMenuItem', TextTrackMenuItem);
16766
+
16767
+ /**
16768
+ * @file off-text-track-menu-item.js
16769
+ */
16770
+
16771
+ /**
16772
+ * A special menu item for turning of a specific type of text track
16773
+ *
16774
+ * @extends TextTrackMenuItem
16775
+ */
16776
+
16777
+ var OffTextTrackMenuItem = function (_TextTrackMenuItem) {
16778
+ inherits(OffTextTrackMenuItem, _TextTrackMenuItem);
16779
+
16780
+ /**
16781
+ * Creates an instance of this class.
16782
+ *
16783
+ * @param {Player} player
16784
+ * The `Player` that this class should be attached to.
16785
+ *
16786
+ * @param {Object} [options]
16787
+ * The key/value store of player options.
16788
+ */
16789
+ function OffTextTrackMenuItem(player, options) {
16790
+ classCallCheck(this, OffTextTrackMenuItem);
16791
+
16792
+ // Create pseudo track info
16793
+ // Requires options['kind']
16794
+ options.track = {
16795
+ player: player,
16796
+ kind: options.kind,
16797
+ kinds: options.kinds,
16798
+ default: false,
16799
+ mode: 'disabled'
16800
+ };
16801
+
16802
+ if (!options.kinds) {
16803
+ options.kinds = [options.kind];
16804
+ }
16805
+
16806
+ if (options.label) {
16807
+ options.track.label = options.label;
16808
+ } else {
16809
+ options.track.label = options.kinds.join(' and ') + ' off';
16810
+ }
16811
+
16812
+ // MenuItem is selectable
16813
+ options.selectable = true;
16814
+ // MenuItem is NOT multiSelectable (i.e. only one can be marked "selected" at a time)
16815
+ options.multiSelectable = false;
16816
+
16817
+ return possibleConstructorReturn(this, _TextTrackMenuItem.call(this, player, options));
16818
+ }
16819
+
16820
+ /**
16821
+ * Handle text track change
16822
+ *
16823
+ * @param {EventTarget~Event} event
16824
+ * The event that caused this function to run
16825
+ */
16826
+
16827
+
16828
+ OffTextTrackMenuItem.prototype.handleTracksChange = function handleTracksChange(event) {
16829
+ var tracks = this.player().textTracks();
16830
+ var shouldBeSelected = true;
16831
+
16832
+ for (var i = 0, l = tracks.length; i < l; i++) {
16833
+ var track = tracks[i];
16834
+
16835
+ if (this.options_.kinds.indexOf(track.kind) > -1 && track.mode === 'showing') {
16836
+ shouldBeSelected = false;
16837
+ break;
16838
+ }
16839
+ }
16840
+
16841
+ // Prevent redundant selected() calls because they may cause
16842
+ // screen readers to read the appended control text unnecessarily
16843
+ if (shouldBeSelected !== this.isSelected_) {
16844
+ this.selected(shouldBeSelected);
16845
+ }
16846
+ };
16847
+
16848
+ OffTextTrackMenuItem.prototype.handleSelectedLanguageChange = function handleSelectedLanguageChange(event) {
16849
+ var tracks = this.player().textTracks();
16850
+ var allHidden = true;
16851
+
16852
+ for (var i = 0, l = tracks.length; i < l; i++) {
16853
+ var track = tracks[i];
16854
+
16855
+ if (['captions', 'descriptions', 'subtitles'].indexOf(track.kind) > -1 && track.mode === 'showing') {
16856
+ allHidden = false;
16857
+ break;
16858
+ }
16859
+ }
16860
+
16861
+ if (allHidden) {
16862
+ this.player_.cache_.selectedLanguage = {
16863
+ enabled: false
16864
+ };
16865
+ }
16866
+ };
16867
+
16868
+ return OffTextTrackMenuItem;
16869
+ }(TextTrackMenuItem);
16870
+
16871
+ Component.registerComponent('OffTextTrackMenuItem', OffTextTrackMenuItem);
16872
+
16873
+ /**
16874
+ * @file text-track-button.js
16875
+ */
16876
+
16877
+ /**
16878
+ * The base class for buttons that toggle specific text track types (e.g. subtitles)
16879
+ *
16880
+ * @extends MenuButton
16881
+ */
16882
+
16883
+ var TextTrackButton = function (_TrackButton) {
16884
+ inherits(TextTrackButton, _TrackButton);
16885
+
16886
+ /**
16887
+ * Creates an instance of this class.
16888
+ *
16889
+ * @param {Player} player
16890
+ * The `Player` that this class should be attached to.
16891
+ *
16892
+ * @param {Object} [options={}]
16893
+ * The key/value store of player options.
16894
+ */
16895
+ function TextTrackButton(player) {
16896
+ var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
16897
+ classCallCheck(this, TextTrackButton);
16898
+
16899
+ options.tracks = player.textTracks();
16900
+
16901
+ return possibleConstructorReturn(this, _TrackButton.call(this, player, options));
16902
+ }
16903
+
16904
+ /**
16905
+ * Create a menu item for each text track
16906
+ *
16907
+ * @param {TextTrackMenuItem[]} [items=[]]
16908
+ * Existing array of items to use during creation
16909
+ *
16910
+ * @return {TextTrackMenuItem[]}
16911
+ * Array of menu items that were created
16912
+ */
16913
+
16914
+
16915
+ TextTrackButton.prototype.createItems = function createItems() {
16916
+ var items = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
16917
+ var TrackMenuItem = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : TextTrackMenuItem;
16918
+
16919
+
16920
+ // Label is an override for the [track] off label
16921
+ // USed to localise captions/subtitles
16922
+ var label = void 0;
16923
+
16924
+ if (this.label_) {
16925
+ label = this.label_ + ' off';
16926
+ }
16927
+ // Add an OFF menu item to turn all tracks off
16928
+ items.push(new OffTextTrackMenuItem(this.player_, {
16929
+ kinds: this.kinds_,
16930
+ kind: this.kind_,
16931
+ label: label
16932
+ }));
16933
+
16934
+ this.hideThreshold_ += 1;
16935
+
16936
+ var tracks = this.player_.textTracks();
16937
+
16938
+ if (!Array.isArray(this.kinds_)) {
16939
+ this.kinds_ = [this.kind_];
16940
+ }
16941
+
16942
+ for (var i = 0; i < tracks.length; i++) {
16943
+ var track = tracks[i];
16944
+
16945
+ // only add tracks that are of an appropriate kind and have a label
16946
+ if (this.kinds_.indexOf(track.kind) > -1) {
16947
+
16948
+ var item = new TrackMenuItem(this.player_, {
16949
+ track: track,
16950
+ // MenuItem is selectable
16951
+ selectable: true,
16952
+ // MenuItem is NOT multiSelectable (i.e. only one can be marked "selected" at a time)
16953
+ multiSelectable: false
16954
+ });
16955
+
16956
+ item.addClass('vjs-' + track.kind + '-menu-item');
16957
+ items.push(item);
16958
+ }
16959
+ }
16960
+
16961
+ return items;
16962
+ };
16963
+
16964
+ return TextTrackButton;
16965
+ }(TrackButton);
16966
+
16967
+ Component.registerComponent('TextTrackButton', TextTrackButton);
16968
+
16969
+ /**
16970
+ * @file chapters-track-menu-item.js
16971
+ */
16972
+
16973
+ /**
16974
+ * The chapter track menu item
16975
+ *
16976
+ * @extends MenuItem
16977
+ */
16978
+
16979
+ var ChaptersTrackMenuItem = function (_MenuItem) {
16980
+ inherits(ChaptersTrackMenuItem, _MenuItem);
16981
+
16982
+ /**
16983
+ * Creates an instance of this class.
16984
+ *
16985
+ * @param {Player} player
16986
+ * The `Player` that this class should be attached to.
16987
+ *
16988
+ * @param {Object} [options]
16989
+ * The key/value store of player options.
16990
+ */
16991
+ function ChaptersTrackMenuItem(player, options) {
16992
+ classCallCheck(this, ChaptersTrackMenuItem);
16993
+
16994
+ var track = options.track;
16995
+ var cue = options.cue;
16996
+ var currentTime = player.currentTime();
16997
+
16998
+ // Modify options for parent MenuItem class's init.
16999
+ options.selectable = true;
17000
+ options.multiSelectable = false;
17001
+ options.label = cue.text;
17002
+ options.selected = cue.startTime <= currentTime && currentTime < cue.endTime;
17003
+
17004
+ var _this = possibleConstructorReturn(this, _MenuItem.call(this, player, options));
17005
+
17006
+ _this.track = track;
17007
+ _this.cue = cue;
17008
+ track.addEventListener('cuechange', bind(_this, _this.update));
17009
+ return _this;
17010
+ }
17011
+
17012
+ /**
17013
+ * This gets called when an `ChaptersTrackMenuItem` is "clicked". See
17014
+ * {@link ClickableComponent} for more detailed information on what a click can be.
17015
+ *
17016
+ * @param {EventTarget~Event} [event]
17017
+ * The `keydown`, `tap`, or `click` event that caused this function to be
17018
+ * called.
17019
+ *
17020
+ * @listens tap
17021
+ * @listens click
17022
+ */
17023
+
17024
+
17025
+ ChaptersTrackMenuItem.prototype.handleClick = function handleClick(event) {
17026
+ _MenuItem.prototype.handleClick.call(this);
17027
+ this.player_.currentTime(this.cue.startTime);
17028
+ this.update(this.cue.startTime);
17029
+ };
17030
+
17031
+ /**
17032
+ * Update chapter menu item
17033
+ *
17034
+ * @param {EventTarget~Event} [event]
17035
+ * The `cuechange` event that caused this function to run.
17036
+ *
17037
+ * @listens TextTrack#cuechange
17038
+ */
17039
+
17040
+
17041
+ ChaptersTrackMenuItem.prototype.update = function update(event) {
17042
+ var cue = this.cue;
17043
+ var currentTime = this.player_.currentTime();
17044
+
17045
+ // vjs.log(currentTime, cue.startTime);
17046
+ this.selected(cue.startTime <= currentTime && currentTime < cue.endTime);
17047
+ };
17048
+
17049
+ return ChaptersTrackMenuItem;
17050
+ }(MenuItem);
17051
+
17052
+ Component.registerComponent('ChaptersTrackMenuItem', ChaptersTrackMenuItem);
17053
+
17054
+ /**
17055
+ * @file chapters-button.js
17056
+ */
17057
+
17058
+ /**
17059
+ * The button component for toggling and selecting chapters
17060
+ * Chapters act much differently than other text tracks
17061
+ * Cues are navigation vs. other tracks of alternative languages
17062
+ *
17063
+ * @extends TextTrackButton
17064
+ */
17065
+
17066
+ var ChaptersButton = function (_TextTrackButton) {
17067
+ inherits(ChaptersButton, _TextTrackButton);
17068
+
17069
+ /**
17070
+ * Creates an instance of this class.
17071
+ *
17072
+ * @param {Player} player
17073
+ * The `Player` that this class should be attached to.
17074
+ *
17075
+ * @param {Object} [options]
17076
+ * The key/value store of player options.
17077
+ *
17078
+ * @param {Component~ReadyCallback} [ready]
17079
+ * The function to call when this function is ready.
17080
+ */
17081
+ function ChaptersButton(player, options, ready) {
17082
+ classCallCheck(this, ChaptersButton);
17083
+ return possibleConstructorReturn(this, _TextTrackButton.call(this, player, options, ready));
17084
+ }
17085
+
17086
+ /**
17087
+ * Builds the default DOM `className`.
17088
+ *
17089
+ * @return {string}
17090
+ * The DOM `className` for this object.
17091
+ */
17092
+
17093
+
17094
+ ChaptersButton.prototype.buildCSSClass = function buildCSSClass() {
17095
+ return 'vjs-chapters-button ' + _TextTrackButton.prototype.buildCSSClass.call(this);
17096
+ };
17097
+
17098
+ ChaptersButton.prototype.buildWrapperCSSClass = function buildWrapperCSSClass() {
17099
+ return 'vjs-chapters-button ' + _TextTrackButton.prototype.buildWrapperCSSClass.call(this);
17100
+ };
17101
+
17102
+ /**
17103
+ * Update the menu based on the current state of its items.
17104
+ *
17105
+ * @param {EventTarget~Event} [event]
17106
+ * An event that triggered this function to run.
17107
+ *
17108
+ * @listens TextTrackList#addtrack
17109
+ * @listens TextTrackList#removetrack
17110
+ * @listens TextTrackList#change
17111
+ */
17112
+
17113
+
17114
+ ChaptersButton.prototype.update = function update(event) {
17115
+ if (!this.track_ || event && (event.type === 'addtrack' || event.type === 'removetrack')) {
17116
+ this.setTrack(this.findChaptersTrack());
17117
+ }
17118
+ _TextTrackButton.prototype.update.call(this);
17119
+ };
17120
+
17121
+ /**
17122
+ * Set the currently selected track for the chapters button.
17123
+ *
17124
+ * @param {TextTrack} track
17125
+ * The new track to select. Nothing will change if this is the currently selected
17126
+ * track.
17127
+ */
17128
+
17129
+
17130
+ ChaptersButton.prototype.setTrack = function setTrack(track) {
17131
+ if (this.track_ === track) {
17132
+ return;
17133
+ }
17134
+
17135
+ if (!this.updateHandler_) {
17136
+ this.updateHandler_ = this.update.bind(this);
17137
+ }
17138
+
17139
+ // here this.track_ refers to the old track instance
17140
+ if (this.track_) {
17141
+ var remoteTextTrackEl = this.player_.remoteTextTrackEls().getTrackElementByTrack_(this.track_);
17142
+
17143
+ if (remoteTextTrackEl) {
17144
+ remoteTextTrackEl.removeEventListener('load', this.updateHandler_);
17145
+ }
17146
+
17147
+ this.track_ = null;
17148
+ }
17149
+
17150
+ this.track_ = track;
17151
+
17152
+ // here this.track_ refers to the new track instance
17153
+ if (this.track_) {
17154
+ this.track_.mode = 'hidden';
17155
+
17156
+ var _remoteTextTrackEl = this.player_.remoteTextTrackEls().getTrackElementByTrack_(this.track_);
17157
+
17158
+ if (_remoteTextTrackEl) {
17159
+ _remoteTextTrackEl.addEventListener('load', this.updateHandler_);
17160
+ }
17161
+ }
17162
+ };
17163
+
17164
+ /**
17165
+ * Find the track object that is currently in use by this ChaptersButton
17166
+ *
17167
+ * @return {TextTrack|undefined}
17168
+ * The current track or undefined if none was found.
17169
+ */
17170
+
17171
+
17172
+ ChaptersButton.prototype.findChaptersTrack = function findChaptersTrack() {
17173
+ var tracks = this.player_.textTracks() || [];
17174
+
17175
+ for (var i = tracks.length - 1; i >= 0; i--) {
17176
+ // We will always choose the last track as our chaptersTrack
17177
+ var track = tracks[i];
17178
+
17179
+ if (track.kind === this.kind_) {
17180
+ return track;
17181
+ }
17182
+ }
17183
+ };
17184
+
17185
+ /**
17186
+ * Get the caption for the ChaptersButton based on the track label. This will also
17187
+ * use the current tracks localized kind as a fallback if a label does not exist.
17188
+ *
17189
+ * @return {string}
17190
+ * The tracks current label or the localized track kind.
17191
+ */
17192
+
17193
+
17194
+ ChaptersButton.prototype.getMenuCaption = function getMenuCaption() {
17195
+ if (this.track_ && this.track_.label) {
17196
+ return this.track_.label;
17197
+ }
17198
+ return this.localize(toTitleCase(this.kind_));
17199
+ };
17200
+
17201
+ /**
17202
+ * Create menu from chapter track
17203
+ *
17204
+ * @return {Menu}
17205
+ * New menu for the chapter buttons
17206
+ */
17207
+
17208
+
17209
+ ChaptersButton.prototype.createMenu = function createMenu() {
17210
+ this.options_.title = this.getMenuCaption();
17211
+ return _TextTrackButton.prototype.createMenu.call(this);
17212
+ };
17213
+
17214
+ /**
17215
+ * Create a menu item for each text track
17216
+ *
17217
+ * @return {TextTrackMenuItem[]}
17218
+ * Array of menu items
17219
+ */
17220
+
17221
+
17222
+ ChaptersButton.prototype.createItems = function createItems() {
17223
+ var items = [];
17224
+
17225
+ if (!this.track_) {
17226
+ return items;
17227
+ }
17228
+
17229
+ var cues = this.track_.cues;
17230
+
17231
+ if (!cues) {
17232
+ return items;
17233
+ }
17234
+
17235
+ for (var i = 0, l = cues.length; i < l; i++) {
17236
+ var cue = cues[i];
17237
+ var mi = new ChaptersTrackMenuItem(this.player_, { track: this.track_, cue: cue });
17238
+
17239
+ items.push(mi);
17240
+ }
17241
+
17242
+ return items;
17243
+ };
17244
+
17245
+ return ChaptersButton;
17246
+ }(TextTrackButton);
17247
+
17248
+ /**
17249
+ * `kind` of TextTrack to look for to associate it with this menu.
17250
+ *
17251
+ * @type {string}
17252
+ * @private
17253
+ */
17254
+
17255
+
17256
+ ChaptersButton.prototype.kind_ = 'chapters';
17257
+
17258
+ /**
17259
+ * The text that should display over the `ChaptersButton`s controls. Added for localization.
17260
+ *
17261
+ * @type {string}
17262
+ * @private
17263
+ */
17264
+ ChaptersButton.prototype.controlText_ = 'Chapters';
17265
+
17266
+ Component.registerComponent('ChaptersButton', ChaptersButton);
17267
+
17268
+ /**
17269
+ * @file descriptions-button.js
17270
+ */
17271
+
17272
+ /**
17273
+ * The button component for toggling and selecting descriptions
17274
+ *
17275
+ * @extends TextTrackButton
17276
+ */
17277
+
17278
+ var DescriptionsButton = function (_TextTrackButton) {
17279
+ inherits(DescriptionsButton, _TextTrackButton);
17280
+
17281
+ /**
17282
+ * Creates an instance of this class.
17283
+ *
17284
+ * @param {Player} player
17285
+ * The `Player` that this class should be attached to.
17286
+ *
17287
+ * @param {Object} [options]
17288
+ * The key/value store of player options.
17289
+ *
17290
+ * @param {Component~ReadyCallback} [ready]
17291
+ * The function to call when this component is ready.
17292
+ */
17293
+ function DescriptionsButton(player, options, ready) {
17294
+ classCallCheck(this, DescriptionsButton);
17295
+
17296
+ var _this = possibleConstructorReturn(this, _TextTrackButton.call(this, player, options, ready));
17297
+
17298
+ var tracks = player.textTracks();
17299
+ var changeHandler = bind(_this, _this.handleTracksChange);
17300
+
17301
+ tracks.addEventListener('change', changeHandler);
17302
+ _this.on('dispose', function () {
17303
+ tracks.removeEventListener('change', changeHandler);
17304
+ });
17305
+ return _this;
17306
+ }
17307
+
17308
+ /**
17309
+ * Handle text track change
17310
+ *
17311
+ * @param {EventTarget~Event} event
17312
+ * The event that caused this function to run
17313
+ *
17314
+ * @listens TextTrackList#change
17315
+ */
17316
+
17317
+
17318
+ DescriptionsButton.prototype.handleTracksChange = function handleTracksChange(event) {
17319
+ var tracks = this.player().textTracks();
17320
+ var disabled = false;
17321
+
17322
+ // Check whether a track of a different kind is showing
17323
+ for (var i = 0, l = tracks.length; i < l; i++) {
17324
+ var track = tracks[i];
17325
+
17326
+ if (track.kind !== this.kind_ && track.mode === 'showing') {
17327
+ disabled = true;
17328
+ break;
17329
+ }
17330
+ }
17331
+
17332
+ // If another track is showing, disable this menu button
17333
+ if (disabled) {
17334
+ this.disable();
17335
+ } else {
17336
+ this.enable();
17337
+ }
17338
+ };
17339
+
17340
+ /**
17341
+ * Builds the default DOM `className`.
17342
+ *
17343
+ * @return {string}
17344
+ * The DOM `className` for this object.
17345
+ */
17346
+
17347
+
17348
+ DescriptionsButton.prototype.buildCSSClass = function buildCSSClass() {
17349
+ return 'vjs-descriptions-button ' + _TextTrackButton.prototype.buildCSSClass.call(this);
17350
+ };
17351
+
17352
+ DescriptionsButton.prototype.buildWrapperCSSClass = function buildWrapperCSSClass() {
17353
+ return 'vjs-descriptions-button ' + _TextTrackButton.prototype.buildWrapperCSSClass.call(this);
17354
+ };
17355
+
17356
+ return DescriptionsButton;
17357
+ }(TextTrackButton);
17358
+
17359
+ /**
17360
+ * `kind` of TextTrack to look for to associate it with this menu.
17361
+ *
17362
+ * @type {string}
17363
+ * @private
17364
+ */
17365
+
17366
+
17367
+ DescriptionsButton.prototype.kind_ = 'descriptions';
17368
+
17369
+ /**
17370
+ * The text that should display over the `DescriptionsButton`s controls. Added for localization.
17371
+ *
17372
+ * @type {string}
17373
+ * @private
17374
+ */
17375
+ DescriptionsButton.prototype.controlText_ = 'Descriptions';
17376
+
17377
+ Component.registerComponent('DescriptionsButton', DescriptionsButton);
17378
+
17379
+ /**
17380
+ * @file subtitles-button.js
17381
+ */
17382
+
17383
+ /**
17384
+ * The button component for toggling and selecting subtitles
17385
+ *
17386
+ * @extends TextTrackButton
17387
+ */
17388
+
17389
+ var SubtitlesButton = function (_TextTrackButton) {
17390
+ inherits(SubtitlesButton, _TextTrackButton);
17391
+
17392
+ /**
17393
+ * Creates an instance of this class.
17394
+ *
17395
+ * @param {Player} player
17396
+ * The `Player` that this class should be attached to.
17397
+ *
17398
+ * @param {Object} [options]
17399
+ * The key/value store of player options.
17400
+ *
17401
+ * @param {Component~ReadyCallback} [ready]
17402
+ * The function to call when this component is ready.
17403
+ */
17404
+ function SubtitlesButton(player, options, ready) {
17405
+ classCallCheck(this, SubtitlesButton);
17406
+ return possibleConstructorReturn(this, _TextTrackButton.call(this, player, options, ready));
17407
+ }
17408
+
17409
+ /**
17410
+ * Builds the default DOM `className`.
17411
+ *
17412
+ * @return {string}
17413
+ * The DOM `className` for this object.
17414
+ */
17415
+
17416
+
17417
+ SubtitlesButton.prototype.buildCSSClass = function buildCSSClass() {
17418
+ return 'vjs-subtitles-button ' + _TextTrackButton.prototype.buildCSSClass.call(this);
17419
+ };
17420
+
17421
+ SubtitlesButton.prototype.buildWrapperCSSClass = function buildWrapperCSSClass() {
17422
+ return 'vjs-subtitles-button ' + _TextTrackButton.prototype.buildWrapperCSSClass.call(this);
17423
+ };
17424
+
17425
+ return SubtitlesButton;
17426
+ }(TextTrackButton);
17427
+
17428
+ /**
17429
+ * `kind` of TextTrack to look for to associate it with this menu.
17430
+ *
17431
+ * @type {string}
17432
+ * @private
17433
+ */
17434
+
17435
+
17436
+ SubtitlesButton.prototype.kind_ = 'subtitles';
17437
+
17438
+ /**
17439
+ * The text that should display over the `SubtitlesButton`s controls. Added for localization.
17440
+ *
17441
+ * @type {string}
17442
+ * @private
17443
+ */
17444
+ SubtitlesButton.prototype.controlText_ = 'Subtitles';
17445
+
17446
+ Component.registerComponent('SubtitlesButton', SubtitlesButton);
17447
+
17448
+ /**
17449
+ * @file caption-settings-menu-item.js
17450
+ */
17451
+
17452
+ /**
17453
+ * The menu item for caption track settings menu
17454
+ *
17455
+ * @extends TextTrackMenuItem
17456
+ */
17457
+
17458
+ var CaptionSettingsMenuItem = function (_TextTrackMenuItem) {
17459
+ inherits(CaptionSettingsMenuItem, _TextTrackMenuItem);
17460
+
17461
+ /**
17462
+ * Creates an instance of this class.
17463
+ *
17464
+ * @param {Player} player
17465
+ * The `Player` that this class should be attached to.
17466
+ *
17467
+ * @param {Object} [options]
17468
+ * The key/value store of player options.
17469
+ */
17470
+ function CaptionSettingsMenuItem(player, options) {
17471
+ classCallCheck(this, CaptionSettingsMenuItem);
17472
+
17473
+ options.track = {
17474
+ player: player,
17475
+ kind: options.kind,
17476
+ label: options.kind + ' settings',
17477
+ selectable: false,
17478
+ default: false,
17479
+ mode: 'disabled'
17480
+ };
17481
+
17482
+ // CaptionSettingsMenuItem has no concept of 'selected'
17483
+ options.selectable = false;
17484
+
17485
+ options.name = 'CaptionSettingsMenuItem';
17486
+
17487
+ var _this = possibleConstructorReturn(this, _TextTrackMenuItem.call(this, player, options));
17488
+
17489
+ _this.addClass('vjs-texttrack-settings');
17490
+ _this.controlText(', opens ' + options.kind + ' settings dialog');
17491
+ return _this;
17492
+ }
17493
+
17494
+ /**
17495
+ * This gets called when an `CaptionSettingsMenuItem` is "clicked". See
17496
+ * {@link ClickableComponent} for more detailed information on what a click can be.
17497
+ *
17498
+ * @param {EventTarget~Event} [event]
17499
+ * The `keydown`, `tap`, or `click` event that caused this function to be
17500
+ * called.
17501
+ *
17502
+ * @listens tap
17503
+ * @listens click
17504
+ */
17505
+
17506
+
17507
+ CaptionSettingsMenuItem.prototype.handleClick = function handleClick(event) {
17508
+ this.player().getChild('textTrackSettings').open();
17509
+ };
17510
+
17511
+ return CaptionSettingsMenuItem;
17512
+ }(TextTrackMenuItem);
17513
+
17514
+ Component.registerComponent('CaptionSettingsMenuItem', CaptionSettingsMenuItem);
17515
+
17516
+ /**
17517
+ * @file captions-button.js
17518
+ */
17519
+
17520
+ /**
17521
+ * The button component for toggling and selecting captions
17522
+ *
17523
+ * @extends TextTrackButton
17524
+ */
17525
+
17526
+ var CaptionsButton = function (_TextTrackButton) {
17527
+ inherits(CaptionsButton, _TextTrackButton);
17528
+
17529
+ /**
17530
+ * Creates an instance of this class.
17531
+ *
17532
+ * @param {Player} player
17533
+ * The `Player` that this class should be attached to.
17534
+ *
17535
+ * @param {Object} [options]
17536
+ * The key/value store of player options.
17537
+ *
17538
+ * @param {Component~ReadyCallback} [ready]
17539
+ * The function to call when this component is ready.
17540
+ */
17541
+ function CaptionsButton(player, options, ready) {
17542
+ classCallCheck(this, CaptionsButton);
17543
+ return possibleConstructorReturn(this, _TextTrackButton.call(this, player, options, ready));
17544
+ }
17545
+
17546
+ /**
17547
+ * Builds the default DOM `className`.
17548
+ *
17549
+ * @return {string}
17550
+ * The DOM `className` for this object.
17551
+ */
17552
+
17553
+
17554
+ CaptionsButton.prototype.buildCSSClass = function buildCSSClass() {
17555
+ return 'vjs-captions-button ' + _TextTrackButton.prototype.buildCSSClass.call(this);
17556
+ };
17557
+
17558
+ CaptionsButton.prototype.buildWrapperCSSClass = function buildWrapperCSSClass() {
17559
+ return 'vjs-captions-button ' + _TextTrackButton.prototype.buildWrapperCSSClass.call(this);
17560
+ };
17561
+
17562
+ /**
17563
+ * Create caption menu items
17564
+ *
17565
+ * @return {CaptionSettingsMenuItem[]}
17566
+ * The array of current menu items.
17567
+ */
17568
+
17569
+
17570
+ CaptionsButton.prototype.createItems = function createItems() {
17571
+ var items = [];
17572
+
17573
+ if (!(this.player().tech_ && this.player().tech_.featuresNativeTextTracks) && this.player().getChild('textTrackSettings')) {
17574
+ items.push(new CaptionSettingsMenuItem(this.player_, { kind: this.kind_ }));
17575
+
17576
+ this.hideThreshold_ += 1;
17577
+ }
17578
+
17579
+ return _TextTrackButton.prototype.createItems.call(this, items);
17580
+ };
17581
+
17582
+ return CaptionsButton;
17583
+ }(TextTrackButton);
17584
+
17585
+ /**
17586
+ * `kind` of TextTrack to look for to associate it with this menu.
17587
+ *
17588
+ * @type {string}
17589
+ * @private
17590
+ */
17591
+
17592
+
17593
+ CaptionsButton.prototype.kind_ = 'captions';
17594
+
17595
+ /**
17596
+ * The text that should display over the `CaptionsButton`s controls. Added for localization.
17597
+ *
17598
+ * @type {string}
17599
+ * @private
17600
+ */
17601
+ CaptionsButton.prototype.controlText_ = 'Captions';
17602
+
17603
+ Component.registerComponent('CaptionsButton', CaptionsButton);
17604
+
17605
+ /**
17606
+ * @file subs-caps-menu-item.js
17607
+ */
17608
+
17609
+ /**
17610
+ * SubsCapsMenuItem has an [cc] icon to distinguish captions from subtitles
17611
+ * in the SubsCapsMenu.
17612
+ *
17613
+ * @extends TextTrackMenuItem
17614
+ */
17615
+
17616
+ var SubsCapsMenuItem = function (_TextTrackMenuItem) {
17617
+ inherits(SubsCapsMenuItem, _TextTrackMenuItem);
17618
+
17619
+ function SubsCapsMenuItem() {
17620
+ classCallCheck(this, SubsCapsMenuItem);
17621
+ return possibleConstructorReturn(this, _TextTrackMenuItem.apply(this, arguments));
17622
+ }
17623
+
17624
+ SubsCapsMenuItem.prototype.createEl = function createEl(type, props, attrs) {
17625
+ var innerHTML = '<span class="vjs-menu-item-text">' + this.localize(this.options_.label);
17626
+
17627
+ if (this.options_.track.kind === 'captions') {
17628
+ innerHTML += '\n <span aria-hidden="true" class="vjs-icon-placeholder"></span>\n <span class="vjs-control-text"> ' + this.localize('Captions') + '</span>\n ';
17629
+ }
17630
+
17631
+ innerHTML += '</span>';
17632
+
17633
+ var el = _TextTrackMenuItem.prototype.createEl.call(this, type, assign({
17634
+ innerHTML: innerHTML
17635
+ }, props), attrs);
17636
+
17637
+ return el;
17638
+ };
17639
+
17640
+ return SubsCapsMenuItem;
17641
+ }(TextTrackMenuItem);
17642
+
17643
+ Component.registerComponent('SubsCapsMenuItem', SubsCapsMenuItem);
17644
+
17645
+ /**
17646
+ * @file sub-caps-button.js
17647
+ */
17648
+ /**
17649
+ * The button component for toggling and selecting captions and/or subtitles
17650
+ *
17651
+ * @extends TextTrackButton
17652
+ */
17653
+
17654
+ var SubsCapsButton = function (_TextTrackButton) {
17655
+ inherits(SubsCapsButton, _TextTrackButton);
17656
+
17657
+ function SubsCapsButton(player) {
17658
+ var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
17659
+ classCallCheck(this, SubsCapsButton);
17660
+
17661
+ // Although North America uses "captions" in most cases for
17662
+ // "captions and subtitles" other locales use "subtitles"
17663
+ var _this = possibleConstructorReturn(this, _TextTrackButton.call(this, player, options));
17664
+
17665
+ _this.label_ = 'subtitles';
17666
+ if (['en', 'en-us', 'en-ca', 'fr-ca'].indexOf(_this.player_.language_) > -1) {
17667
+ _this.label_ = 'captions';
17668
+ }
17669
+ _this.menuButton_.controlText(toTitleCase(_this.label_));
17670
+ return _this;
17671
+ }
17672
+
17673
+ /**
17674
+ * Builds the default DOM `className`.
17675
+ *
17676
+ * @return {string}
17677
+ * The DOM `className` for this object.
17678
+ */
17679
+
17680
+
17681
+ SubsCapsButton.prototype.buildCSSClass = function buildCSSClass() {
17682
+ return 'vjs-subs-caps-button ' + _TextTrackButton.prototype.buildCSSClass.call(this);
17683
+ };
17684
+
17685
+ SubsCapsButton.prototype.buildWrapperCSSClass = function buildWrapperCSSClass() {
17686
+ return 'vjs-subs-caps-button ' + _TextTrackButton.prototype.buildWrapperCSSClass.call(this);
17687
+ };
17688
+
17689
+ /**
17690
+ * Create caption/subtitles menu items
17691
+ *
17692
+ * @return {CaptionSettingsMenuItem[]}
17693
+ * The array of current menu items.
17694
+ */
17695
+
17696
+
17697
+ SubsCapsButton.prototype.createItems = function createItems() {
17698
+ var items = [];
17699
+
17700
+ if (!(this.player().tech_ && this.player().tech_.featuresNativeTextTracks) && this.player().getChild('textTrackSettings')) {
17701
+ items.push(new CaptionSettingsMenuItem(this.player_, { kind: this.label_ }));
17702
+
17703
+ this.hideThreshold_ += 1;
17704
+ }
17705
+
17706
+ items = _TextTrackButton.prototype.createItems.call(this, items, SubsCapsMenuItem);
17707
+ return items;
17708
+ };
17709
+
17710
+ return SubsCapsButton;
17711
+ }(TextTrackButton);
17712
+
17713
+ /**
17714
+ * `kind`s of TextTrack to look for to associate it with this menu.
17715
+ *
17716
+ * @type {array}
17717
+ * @private
17718
+ */
17719
+
17720
+
17721
+ SubsCapsButton.prototype.kinds_ = ['captions', 'subtitles'];
17722
+
17723
+ /**
17724
+ * The text that should display over the `SubsCapsButton`s controls.
17725
+ *
17726
+ *
17727
+ * @type {string}
17728
+ * @private
17729
+ */
17730
+ SubsCapsButton.prototype.controlText_ = 'Subtitles';
17731
+
17732
+ Component.registerComponent('SubsCapsButton', SubsCapsButton);
17733
+
17734
+ /**
17735
+ * @file audio-track-menu-item.js
17736
+ */
17737
+
17738
+ /**
17739
+ * An {@link AudioTrack} {@link MenuItem}
17740
+ *
17741
+ * @extends MenuItem
17742
+ */
17743
+
17744
+ var AudioTrackMenuItem = function (_MenuItem) {
17745
+ inherits(AudioTrackMenuItem, _MenuItem);
17746
+
17747
+ /**
17748
+ * Creates an instance of this class.
17749
+ *
17750
+ * @param {Player} player
17751
+ * The `Player` that this class should be attached to.
17752
+ *
17753
+ * @param {Object} [options]
17754
+ * The key/value store of player options.
17755
+ */
17756
+ function AudioTrackMenuItem(player, options) {
17757
+ classCallCheck(this, AudioTrackMenuItem);
17758
+
17759
+ var track = options.track;
17760
+ var tracks = player.audioTracks();
17761
+
17762
+ // Modify options for parent MenuItem class's init.
17763
+ options.label = track.label || track.language || 'Unknown';
17764
+ options.selected = track.enabled;
17765
+
17766
+ var _this = possibleConstructorReturn(this, _MenuItem.call(this, player, options));
17767
+
17768
+ _this.track = track;
17769
+
17770
+ _this.addClass('vjs-' + track.kind + '-menu-item');
17771
+
17772
+ var changeHandler = function changeHandler() {
17773
+ for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
17774
+ args[_key] = arguments[_key];
17775
+ }
17776
+
17777
+ _this.handleTracksChange.apply(_this, args);
17778
+ };
17779
+
17780
+ tracks.addEventListener('change', changeHandler);
17781
+ _this.on('dispose', function () {
17782
+ tracks.removeEventListener('change', changeHandler);
17783
+ });
17784
+ return _this;
17785
+ }
17786
+
17787
+ AudioTrackMenuItem.prototype.createEl = function createEl(type, props, attrs) {
17788
+ var innerHTML = '<span class="vjs-menu-item-text">' + this.localize(this.options_.label);
17789
+
17790
+ if (this.options_.track.kind === 'main-desc') {
17791
+ innerHTML += '\n <span aria-hidden="true" class="vjs-icon-placeholder"></span>\n <span class="vjs-control-text"> ' + this.localize('Descriptions') + '</span>\n ';
17792
+ }
17793
+
17794
+ innerHTML += '</span>';
17795
+
17796
+ var el = _MenuItem.prototype.createEl.call(this, type, assign({
17797
+ innerHTML: innerHTML
17798
+ }, props), attrs);
17799
+
17800
+ return el;
17801
+ };
17802
+
17803
+ /**
17804
+ * This gets called when an `AudioTrackMenuItem is "clicked". See {@link ClickableComponent}
17805
+ * for more detailed information on what a click can be.
17806
+ *
17807
+ * @param {EventTarget~Event} [event]
17808
+ * The `keydown`, `tap`, or `click` event that caused this function to be
17809
+ * called.
17810
+ *
17811
+ * @listens tap
17812
+ * @listens click
17813
+ */
17814
+
17815
+
17816
+ AudioTrackMenuItem.prototype.handleClick = function handleClick(event) {
17817
+ var tracks = this.player_.audioTracks();
17818
+
17819
+ _MenuItem.prototype.handleClick.call(this, event);
17820
+
17821
+ for (var i = 0; i < tracks.length; i++) {
17822
+ var track = tracks[i];
17823
+
17824
+ track.enabled = track === this.track;
17825
+ }
17826
+ };
17827
+
17828
+ /**
17829
+ * Handle any {@link AudioTrack} change.
17830
+ *
17831
+ * @param {EventTarget~Event} [event]
17832
+ * The {@link AudioTrackList#change} event that caused this to run.
17833
+ *
17834
+ * @listens AudioTrackList#change
17835
+ */
17836
+
17837
+
17838
+ AudioTrackMenuItem.prototype.handleTracksChange = function handleTracksChange(event) {
17839
+ this.selected(this.track.enabled);
17840
+ };
17841
+
17842
+ return AudioTrackMenuItem;
17843
+ }(MenuItem);
17844
+
17845
+ Component.registerComponent('AudioTrackMenuItem', AudioTrackMenuItem);
17846
+
17847
+ /**
17848
+ * @file audio-track-button.js
17849
+ */
17850
+
17851
+ /**
17852
+ * The base class for buttons that toggle specific {@link AudioTrack} types.
17853
+ *
17854
+ * @extends TrackButton
17855
+ */
17856
+
17857
+ var AudioTrackButton = function (_TrackButton) {
17858
+ inherits(AudioTrackButton, _TrackButton);
17859
+
17860
+ /**
17861
+ * Creates an instance of this class.
17862
+ *
17863
+ * @param {Player} player
17864
+ * The `Player` that this class should be attached to.
17865
+ *
17866
+ * @param {Object} [options={}]
17867
+ * The key/value store of player options.
17868
+ */
17869
+ function AudioTrackButton(player) {
17870
+ var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
17871
+ classCallCheck(this, AudioTrackButton);
17872
+
17873
+ options.tracks = player.audioTracks();
17874
+
17875
+ return possibleConstructorReturn(this, _TrackButton.call(this, player, options));
17876
+ }
17877
+
17878
+ /**
17879
+ * Builds the default DOM `className`.
17880
+ *
17881
+ * @return {string}
17882
+ * The DOM `className` for this object.
17883
+ */
17884
+
17885
+
17886
+ AudioTrackButton.prototype.buildCSSClass = function buildCSSClass() {
17887
+ return 'vjs-audio-button ' + _TrackButton.prototype.buildCSSClass.call(this);
17888
+ };
17889
+
17890
+ AudioTrackButton.prototype.buildWrapperCSSClass = function buildWrapperCSSClass() {
17891
+ return 'vjs-audio-button ' + _TrackButton.prototype.buildWrapperCSSClass.call(this);
17892
+ };
17893
+
17894
+ /**
17895
+ * Create a menu item for each audio track
17896
+ *
17897
+ * @param {AudioTrackMenuItem[]} [items=[]]
17898
+ * An array of existing menu items to use.
17899
+ *
17900
+ * @return {AudioTrackMenuItem[]}
17901
+ * An array of menu items
17902
+ */
17903
+
17904
+
17905
+ AudioTrackButton.prototype.createItems = function createItems() {
17906
+ var items = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
17907
+
17908
+ // if there's only one audio track, there no point in showing it
17909
+ this.hideThreshold_ = 1;
17910
+
17911
+ var tracks = this.player_.audioTracks();
17912
+
17913
+ for (var i = 0; i < tracks.length; i++) {
17914
+ var track = tracks[i];
17915
+
17916
+ items.push(new AudioTrackMenuItem(this.player_, {
17917
+ track: track,
17918
+ // MenuItem is selectable
17919
+ selectable: true,
17920
+ // MenuItem is NOT multiSelectable (i.e. only one can be marked "selected" at a time)
17921
+ multiSelectable: false
17922
+ }));
17923
+ }
17924
+
17925
+ return items;
17926
+ };
17927
+
17928
+ return AudioTrackButton;
17929
+ }(TrackButton);
17930
+
17931
+ /**
17932
+ * The text that should display over the `AudioTrackButton`s controls. Added for localization.
17933
+ *
17934
+ * @type {string}
17935
+ * @private
17936
+ */
17937
+
17938
+
17939
+ AudioTrackButton.prototype.controlText_ = 'Audio Track';
17940
+ Component.registerComponent('AudioTrackButton', AudioTrackButton);
17941
+
17942
+ /**
17943
+ * @file playback-rate-menu-item.js
17944
+ */
17945
+
17946
+ /**
17947
+ * The specific menu item type for selecting a playback rate.
17948
+ *
17949
+ * @extends MenuItem
17950
+ */
17951
+
17952
+ var PlaybackRateMenuItem = function (_MenuItem) {
17953
+ inherits(PlaybackRateMenuItem, _MenuItem);
17954
+
17955
+ /**
17956
+ * Creates an instance of this class.
17957
+ *
17958
+ * @param {Player} player
17959
+ * The `Player` that this class should be attached to.
17960
+ *
17961
+ * @param {Object} [options]
17962
+ * The key/value store of player options.
17963
+ */
17964
+ function PlaybackRateMenuItem(player, options) {
17965
+ classCallCheck(this, PlaybackRateMenuItem);
17966
+
17967
+ var label = options.rate;
17968
+ var rate = parseFloat(label, 10);
17969
+
17970
+ // Modify options for parent MenuItem class's init.
17971
+ options.label = label;
17972
+ options.selected = rate === 1;
17973
+ options.selectable = true;
17974
+ options.multiSelectable = false;
17975
+
17976
+ var _this = possibleConstructorReturn(this, _MenuItem.call(this, player, options));
17977
+
17978
+ _this.label = label;
17979
+ _this.rate = rate;
17980
+
17981
+ _this.on(player, 'ratechange', _this.update);
17982
+ return _this;
17983
+ }
17984
+
17985
+ /**
17986
+ * This gets called when an `PlaybackRateMenuItem` is "clicked". See
17987
+ * {@link ClickableComponent} for more detailed information on what a click can be.
17988
+ *
17989
+ * @param {EventTarget~Event} [event]
17990
+ * The `keydown`, `tap`, or `click` event that caused this function to be
17991
+ * called.
17992
+ *
17993
+ * @listens tap
17994
+ * @listens click
17995
+ */
17996
+
17997
+
17998
+ PlaybackRateMenuItem.prototype.handleClick = function handleClick(event) {
17999
+ _MenuItem.prototype.handleClick.call(this);
18000
+ this.player().playbackRate(this.rate);
18001
+ };
18002
+
18003
+ /**
18004
+ * Update the PlaybackRateMenuItem when the playbackrate changes.
18005
+ *
18006
+ * @param {EventTarget~Event} [event]
18007
+ * The `ratechange` event that caused this function to run.
18008
+ *
18009
+ * @listens Player#ratechange
18010
+ */
18011
+
18012
+
18013
+ PlaybackRateMenuItem.prototype.update = function update(event) {
18014
+ this.selected(this.player().playbackRate() === this.rate);
18015
+ };
18016
+
18017
+ return PlaybackRateMenuItem;
18018
+ }(MenuItem);
18019
+
18020
+ /**
18021
+ * The text that should display over the `PlaybackRateMenuItem`s controls. Added for localization.
18022
+ *
18023
+ * @type {string}
18024
+ * @private
18025
+ */
18026
+
18027
+
18028
+ PlaybackRateMenuItem.prototype.contentElType = 'button';
18029
+
18030
+ Component.registerComponent('PlaybackRateMenuItem', PlaybackRateMenuItem);
18031
+
18032
+ /**
18033
+ * @file playback-rate-menu-button.js
18034
+ */
18035
+
18036
+ /**
18037
+ * The component for controlling the playback rate.
18038
+ *
18039
+ * @extends MenuButton
18040
+ */
18041
+
18042
+ var PlaybackRateMenuButton = function (_MenuButton) {
18043
+ inherits(PlaybackRateMenuButton, _MenuButton);
18044
+
18045
+ /**
18046
+ * Creates an instance of this class.
18047
+ *
18048
+ * @param {Player} player
18049
+ * The `Player` that this class should be attached to.
18050
+ *
18051
+ * @param {Object} [options]
18052
+ * The key/value store of player options.
18053
+ */
18054
+ function PlaybackRateMenuButton(player, options) {
18055
+ classCallCheck(this, PlaybackRateMenuButton);
18056
+
18057
+ var _this = possibleConstructorReturn(this, _MenuButton.call(this, player, options));
18058
+
18059
+ _this.updateVisibility();
18060
+ _this.updateLabel();
18061
+
18062
+ _this.on(player, 'loadstart', _this.updateVisibility);
18063
+ _this.on(player, 'ratechange', _this.updateLabel);
18064
+ return _this;
18065
+ }
18066
+
18067
+ /**
18068
+ * Create the `Component`'s DOM element
18069
+ *
18070
+ * @return {Element}
18071
+ * The element that was created.
18072
+ */
18073
+
18074
+
18075
+ PlaybackRateMenuButton.prototype.createEl = function createEl$$1() {
18076
+ var el = _MenuButton.prototype.createEl.call(this);
18077
+
18078
+ this.labelEl_ = createEl('div', {
18079
+ className: 'vjs-playback-rate-value',
18080
+ innerHTML: '1x'
18081
+ });
18082
+
18083
+ el.appendChild(this.labelEl_);
18084
+
18085
+ return el;
18086
+ };
18087
+
18088
+ PlaybackRateMenuButton.prototype.dispose = function dispose() {
18089
+ this.labelEl_ = null;
18090
+
18091
+ _MenuButton.prototype.dispose.call(this);
18092
+ };
18093
+
18094
+ /**
18095
+ * Builds the default DOM `className`.
18096
+ *
18097
+ * @return {string}
18098
+ * The DOM `className` for this object.
18099
+ */
18100
+
18101
+
18102
+ PlaybackRateMenuButton.prototype.buildCSSClass = function buildCSSClass() {
18103
+ return 'vjs-playback-rate ' + _MenuButton.prototype.buildCSSClass.call(this);
18104
+ };
18105
+
18106
+ PlaybackRateMenuButton.prototype.buildWrapperCSSClass = function buildWrapperCSSClass() {
18107
+ return 'vjs-playback-rate ' + _MenuButton.prototype.buildWrapperCSSClass.call(this);
18108
+ };
18109
+
18110
+ /**
18111
+ * Create the playback rate menu
18112
+ *
18113
+ * @return {Menu}
18114
+ * Menu object populated with {@link PlaybackRateMenuItem}s
18115
+ */
18116
+
18117
+
18118
+ PlaybackRateMenuButton.prototype.createMenu = function createMenu() {
18119
+ var menu = new Menu(this.player());
18120
+ var rates = this.playbackRates();
18121
+
18122
+ if (rates) {
18123
+ for (var i = rates.length - 1; i >= 0; i--) {
18124
+ menu.addChild(new PlaybackRateMenuItem(this.player(), { rate: rates[i] + 'x' }));
18125
+ }
18126
+ }
18127
+
18128
+ return menu;
18129
+ };
18130
+
18131
+ /**
18132
+ * Updates ARIA accessibility attributes
18133
+ */
18134
+
18135
+
18136
+ PlaybackRateMenuButton.prototype.updateARIAAttributes = function updateARIAAttributes() {
18137
+ // Current playback rate
18138
+ this.el().setAttribute('aria-valuenow', this.player().playbackRate());
18139
+ };
18140
+
18141
+ /**
18142
+ * This gets called when an `PlaybackRateMenuButton` is "clicked". See
18143
+ * {@link ClickableComponent} for more detailed information on what a click can be.
18144
+ *
18145
+ * @param {EventTarget~Event} [event]
18146
+ * The `keydown`, `tap`, or `click` event that caused this function to be
18147
+ * called.
18148
+ *
18149
+ * @listens tap
18150
+ * @listens click
18151
+ */
18152
+
18153
+
18154
+ PlaybackRateMenuButton.prototype.handleClick = function handleClick(event) {
18155
+ // select next rate option
18156
+ var currentRate = this.player().playbackRate();
18157
+ var rates = this.playbackRates();
18158
+
18159
+ // this will select first one if the last one currently selected
18160
+ var newRate = rates[0];
18161
+
18162
+ for (var i = 0; i < rates.length; i++) {
18163
+ if (rates[i] > currentRate) {
18164
+ newRate = rates[i];
18165
+ break;
18166
+ }
18167
+ }
18168
+ this.player().playbackRate(newRate);
18169
+ };
18170
+
18171
+ /**
18172
+ * Get possible playback rates
18173
+ *
18174
+ * @return {Array}
18175
+ * All possible playback rates
18176
+ */
18177
+
18178
+
18179
+ PlaybackRateMenuButton.prototype.playbackRates = function playbackRates() {
18180
+ return this.options_.playbackRates || this.options_.playerOptions && this.options_.playerOptions.playbackRates;
18181
+ };
18182
+
18183
+ /**
18184
+ * Get whether playback rates is supported by the tech
18185
+ * and an array of playback rates exists
18186
+ *
18187
+ * @return {boolean}
18188
+ * Whether changing playback rate is supported
18189
+ */
18190
+
18191
+
18192
+ PlaybackRateMenuButton.prototype.playbackRateSupported = function playbackRateSupported() {
18193
+ return this.player().tech_ && this.player().tech_.featuresPlaybackRate && this.playbackRates() && this.playbackRates().length > 0;
18194
+ };
18195
+
18196
+ /**
18197
+ * Hide playback rate controls when they're no playback rate options to select
18198
+ *
18199
+ * @param {EventTarget~Event} [event]
18200
+ * The event that caused this function to run.
18201
+ *
18202
+ * @listens Player#loadstart
18203
+ */
18204
+
18205
+
18206
+ PlaybackRateMenuButton.prototype.updateVisibility = function updateVisibility(event) {
18207
+ if (this.playbackRateSupported()) {
18208
+ this.removeClass('vjs-hidden');
18209
+ } else {
18210
+ this.addClass('vjs-hidden');
18211
+ }
18212
+ };
18213
+
18214
+ /**
18215
+ * Update button label when rate changed
18216
+ *
18217
+ * @param {EventTarget~Event} [event]
18218
+ * The event that caused this function to run.
18219
+ *
18220
+ * @listens Player#ratechange
18221
+ */
18222
+
18223
+
18224
+ PlaybackRateMenuButton.prototype.updateLabel = function updateLabel(event) {
18225
+ if (this.playbackRateSupported()) {
18226
+ this.labelEl_.innerHTML = this.player().playbackRate() + 'x';
18227
+ }
18228
+ };
18229
+
18230
+ return PlaybackRateMenuButton;
18231
+ }(MenuButton);
18232
+
18233
+ /**
18234
+ * The text that should display over the `FullscreenToggle`s controls. Added for localization.
18235
+ *
18236
+ * @type {string}
18237
+ * @private
18238
+ */
18239
+
18240
+
18241
+ PlaybackRateMenuButton.prototype.controlText_ = 'Playback Rate';
18242
+
18243
+ Component.registerComponent('PlaybackRateMenuButton', PlaybackRateMenuButton);
18244
+
18245
+ /**
18246
+ * @file spacer.js
18247
+ */
18248
+
18249
+ /**
18250
+ * Just an empty spacer element that can be used as an append point for plugins, etc.
18251
+ * Also can be used to create space between elements when necessary.
18252
+ *
18253
+ * @extends Component
18254
+ */
18255
+
18256
+ var Spacer = function (_Component) {
18257
+ inherits(Spacer, _Component);
18258
+
18259
+ function Spacer() {
18260
+ classCallCheck(this, Spacer);
18261
+ return possibleConstructorReturn(this, _Component.apply(this, arguments));
18262
+ }
18263
+
18264
+ /**
18265
+ * Builds the default DOM `className`.
18266
+ *
18267
+ * @return {string}
18268
+ * The DOM `className` for this object.
18269
+ */
18270
+ Spacer.prototype.buildCSSClass = function buildCSSClass() {
18271
+ return 'vjs-spacer ' + _Component.prototype.buildCSSClass.call(this);
18272
+ };
18273
+
18274
+ /**
18275
+ * Create the `Component`'s DOM element
18276
+ *
18277
+ * @return {Element}
18278
+ * The element that was created.
18279
+ */
18280
+
18281
+
18282
+ Spacer.prototype.createEl = function createEl() {
18283
+ return _Component.prototype.createEl.call(this, 'div', {
18284
+ className: this.buildCSSClass()
18285
+ });
18286
+ };
18287
+
18288
+ return Spacer;
18289
+ }(Component);
18290
+
18291
+ Component.registerComponent('Spacer', Spacer);
18292
+
18293
+ /**
18294
+ * @file custom-control-spacer.js
18295
+ */
18296
+
18297
+ /**
18298
+ * Spacer specifically meant to be used as an insertion point for new plugins, etc.
18299
+ *
18300
+ * @extends Spacer
18301
+ */
18302
+
18303
+ var CustomControlSpacer = function (_Spacer) {
18304
+ inherits(CustomControlSpacer, _Spacer);
18305
+
18306
+ function CustomControlSpacer() {
18307
+ classCallCheck(this, CustomControlSpacer);
18308
+ return possibleConstructorReturn(this, _Spacer.apply(this, arguments));
18309
+ }
18310
+
18311
+ /**
18312
+ * Builds the default DOM `className`.
18313
+ *
18314
+ * @return {string}
18315
+ * The DOM `className` for this object.
18316
+ */
18317
+ CustomControlSpacer.prototype.buildCSSClass = function buildCSSClass() {
18318
+ return 'vjs-custom-control-spacer ' + _Spacer.prototype.buildCSSClass.call(this);
18319
+ };
18320
+
18321
+ /**
18322
+ * Create the `Component`'s DOM element
18323
+ *
18324
+ * @return {Element}
18325
+ * The element that was created.
18326
+ */
18327
+
18328
+
18329
+ CustomControlSpacer.prototype.createEl = function createEl() {
18330
+ var el = _Spacer.prototype.createEl.call(this, {
18331
+ className: this.buildCSSClass()
18332
+ });
18333
+
18334
+ // No-flex/table-cell mode requires there be some content
18335
+ // in the cell to fill the remaining space of the table.
18336
+ el.innerHTML = '\xA0';
18337
+ return el;
18338
+ };
18339
+
18340
+ return CustomControlSpacer;
18341
+ }(Spacer);
18342
+
18343
+ Component.registerComponent('CustomControlSpacer', CustomControlSpacer);
18344
+
18345
+ /**
18346
+ * @file control-bar.js
18347
+ */
18348
+
18349
+ /**
18350
+ * Container of main controls.
18351
+ *
18352
+ * @extends Component
18353
+ */
18354
+
18355
+ var ControlBar = function (_Component) {
18356
+ inherits(ControlBar, _Component);
18357
+
18358
+ function ControlBar() {
18359
+ classCallCheck(this, ControlBar);
18360
+ return possibleConstructorReturn(this, _Component.apply(this, arguments));
18361
+ }
18362
+
18363
+ /**
18364
+ * Create the `Component`'s DOM element
18365
+ *
18366
+ * @return {Element}
18367
+ * The element that was created.
18368
+ */
18369
+ ControlBar.prototype.createEl = function createEl() {
18370
+ return _Component.prototype.createEl.call(this, 'div', {
18371
+ className: 'vjs-control-bar',
18372
+ dir: 'ltr'
18373
+ });
18374
+ };
18375
+
18376
+ return ControlBar;
18377
+ }(Component);
18378
+
18379
+ /**
18380
+ * Default options for `ControlBar`
18381
+ *
18382
+ * @type {Object}
18383
+ * @private
18384
+ */
18385
+
18386
+
18387
+ ControlBar.prototype.options_ = {
18388
+ children: ['playToggle', 'volumePanel', 'currentTimeDisplay', 'timeDivider', 'durationDisplay', 'progressControl', 'liveDisplay', 'remainingTimeDisplay', 'customControlSpacer', 'playbackRateMenuButton', 'chaptersButton', 'descriptionsButton', 'subsCapsButton', 'audioTrackButton', 'fullscreenToggle']
18389
+ };
18390
+
18391
+ Component.registerComponent('ControlBar', ControlBar);
18392
+
18393
+ /**
18394
+ * @file error-display.js
18395
+ */
18396
+
18397
+ /**
18398
+ * A display that indicates an error has occurred. This means that the video
18399
+ * is unplayable.
18400
+ *
18401
+ * @extends ModalDialog
18402
+ */
18403
+
18404
+ var ErrorDisplay = function (_ModalDialog) {
18405
+ inherits(ErrorDisplay, _ModalDialog);
18406
+
18407
+ /**
18408
+ * Creates an instance of this class.
18409
+ *
18410
+ * @param {Player} player
18411
+ * The `Player` that this class should be attached to.
18412
+ *
18413
+ * @param {Object} [options]
18414
+ * The key/value store of player options.
18415
+ */
18416
+ function ErrorDisplay(player, options) {
18417
+ classCallCheck(this, ErrorDisplay);
18418
+
18419
+ var _this = possibleConstructorReturn(this, _ModalDialog.call(this, player, options));
18420
+
18421
+ _this.on(player, 'error', _this.open);
18422
+ return _this;
18423
+ }
18424
+
18425
+ /**
18426
+ * Builds the default DOM `className`.
18427
+ *
18428
+ * @return {string}
18429
+ * The DOM `className` for this object.
18430
+ *
18431
+ * @deprecated Since version 5.
18432
+ */
18433
+
18434
+
18435
+ ErrorDisplay.prototype.buildCSSClass = function buildCSSClass() {
18436
+ return 'vjs-error-display ' + _ModalDialog.prototype.buildCSSClass.call(this);
18437
+ };
18438
+
18439
+ /**
18440
+ * Gets the localized error message based on the `Player`s error.
18441
+ *
18442
+ * @return {string}
18443
+ * The `Player`s error message localized or an empty string.
18444
+ */
18445
+
18446
+
18447
+ ErrorDisplay.prototype.content = function content() {
18448
+ var error = this.player().error();
18449
+
18450
+ return error ? this.localize(error.message) : '';
18451
+ };
18452
+
18453
+ return ErrorDisplay;
18454
+ }(ModalDialog);
18455
+
18456
+ /**
18457
+ * The default options for an `ErrorDisplay`.
18458
+ *
18459
+ * @private
18460
+ */
18461
+
18462
+
18463
+ ErrorDisplay.prototype.options_ = mergeOptions(ModalDialog.prototype.options_, {
18464
+ pauseOnOpen: false,
18465
+ fillAlways: true,
18466
+ temporary: false,
18467
+ uncloseable: true
18468
+ });
18469
+
18470
+ Component.registerComponent('ErrorDisplay', ErrorDisplay);
18471
+
18472
+ /**
18473
+ * @file text-track-settings.js
18474
+ */
18475
+
18476
+ var LOCAL_STORAGE_KEY = 'vjs-text-track-settings';
18477
+
18478
+ var COLOR_BLACK = ['#000', 'Black'];
18479
+ var COLOR_BLUE = ['#00F', 'Blue'];
18480
+ var COLOR_CYAN = ['#0FF', 'Cyan'];
18481
+ var COLOR_GREEN = ['#0F0', 'Green'];
18482
+ var COLOR_MAGENTA = ['#F0F', 'Magenta'];
18483
+ var COLOR_RED = ['#F00', 'Red'];
18484
+ var COLOR_WHITE = ['#FFF', 'White'];
18485
+ var COLOR_YELLOW = ['#FF0', 'Yellow'];
18486
+
18487
+ var OPACITY_OPAQUE = ['1', 'Opaque'];
18488
+ var OPACITY_SEMI = ['0.5', 'Semi-Transparent'];
18489
+ var OPACITY_TRANS = ['0', 'Transparent'];
18490
+
18491
+ // Configuration for the various <select> elements in the DOM of this component.
18492
+ //
18493
+ // Possible keys include:
18494
+ //
18495
+ // `default`:
18496
+ // The default option index. Only needs to be provided if not zero.
18497
+ // `parser`:
18498
+ // A function which is used to parse the value from the selected option in
18499
+ // a customized way.
18500
+ // `selector`:
18501
+ // The selector used to find the associated <select> element.
18502
+ var selectConfigs = {
18503
+ backgroundColor: {
18504
+ selector: '.vjs-bg-color > select',
18505
+ id: 'captions-background-color-%s',
18506
+ label: 'Color',
18507
+ options: [COLOR_BLACK, COLOR_WHITE, COLOR_RED, COLOR_GREEN, COLOR_BLUE, COLOR_YELLOW, COLOR_MAGENTA, COLOR_CYAN]
18508
+ },
18509
+
18510
+ backgroundOpacity: {
18511
+ selector: '.vjs-bg-opacity > select',
18512
+ id: 'captions-background-opacity-%s',
18513
+ label: 'Transparency',
18514
+ options: [OPACITY_OPAQUE, OPACITY_SEMI, OPACITY_TRANS]
18515
+ },
18516
+
18517
+ color: {
18518
+ selector: '.vjs-fg-color > select',
18519
+ id: 'captions-foreground-color-%s',
18520
+ label: 'Color',
18521
+ options: [COLOR_WHITE, COLOR_BLACK, COLOR_RED, COLOR_GREEN, COLOR_BLUE, COLOR_YELLOW, COLOR_MAGENTA, COLOR_CYAN]
18522
+ },
18523
+
18524
+ edgeStyle: {
18525
+ selector: '.vjs-edge-style > select',
18526
+ id: '%s',
18527
+ label: 'Text Edge Style',
18528
+ options: [['none', 'None'], ['raised', 'Raised'], ['depressed', 'Depressed'], ['uniform', 'Uniform'], ['dropshadow', 'Dropshadow']]
18529
+ },
18530
+
18531
+ fontFamily: {
18532
+ selector: '.vjs-font-family > select',
18533
+ id: 'captions-font-family-%s',
18534
+ label: 'Font Family',
18535
+ options: [['proportionalSansSerif', 'Proportional Sans-Serif'], ['monospaceSansSerif', 'Monospace Sans-Serif'], ['proportionalSerif', 'Proportional Serif'], ['monospaceSerif', 'Monospace Serif'], ['casual', 'Casual'], ['script', 'Script'], ['small-caps', 'Small Caps']]
18536
+ },
18537
+
18538
+ fontPercent: {
18539
+ selector: '.vjs-font-percent > select',
18540
+ id: 'captions-font-size-%s',
18541
+ label: 'Font Size',
18542
+ options: [['0.50', '50%'], ['0.75', '75%'], ['1.00', '100%'], ['1.25', '125%'], ['1.50', '150%'], ['1.75', '175%'], ['2.00', '200%'], ['3.00', '300%'], ['4.00', '400%']],
18543
+ default: 2,
18544
+ parser: function parser(v) {
18545
+ return v === '1.00' ? null : Number(v);
18546
+ }
18547
+ },
18548
+
18549
+ textOpacity: {
18550
+ selector: '.vjs-text-opacity > select',
18551
+ id: 'captions-foreground-opacity-%s',
18552
+ label: 'Transparency',
18553
+ options: [OPACITY_OPAQUE, OPACITY_SEMI]
18554
+ },
18555
+
18556
+ // Options for this object are defined below.
18557
+ windowColor: {
18558
+ selector: '.vjs-window-color > select',
18559
+ id: 'captions-window-color-%s',
18560
+ label: 'Color'
18561
+ },
18562
+
18563
+ // Options for this object are defined below.
18564
+ windowOpacity: {
18565
+ selector: '.vjs-window-opacity > select',
18566
+ id: 'captions-window-opacity-%s',
18567
+ label: 'Transparency',
18568
+ options: [OPACITY_TRANS, OPACITY_SEMI, OPACITY_OPAQUE]
18569
+ }
18570
+ };
18571
+
18572
+ selectConfigs.windowColor.options = selectConfigs.backgroundColor.options;
18573
+
18574
+ /**
18575
+ * Get the actual value of an option.
18576
+ *
18577
+ * @param {string} value
18578
+ * The value to get
18579
+ *
18580
+ * @param {Function} [parser]
18581
+ * Optional function to adjust the value.
18582
+ *
18583
+ * @return {Mixed}
18584
+ * - Will be `undefined` if no value exists
18585
+ * - Will be `undefined` if the given value is "none".
18586
+ * - Will be the actual value otherwise.
18587
+ *
18588
+ * @private
18589
+ */
18590
+ function parseOptionValue(value, parser) {
18591
+ if (parser) {
18592
+ value = parser(value);
18593
+ }
18594
+
18595
+ if (value && value !== 'none') {
18596
+ return value;
18597
+ }
18598
+ }
18599
+
18600
+ /**
18601
+ * Gets the value of the selected <option> element within a <select> element.
18602
+ *
18603
+ * @param {Element} el
18604
+ * the element to look in
18605
+ *
18606
+ * @param {Function} [parser]
18607
+ * Optional function to adjust the value.
18608
+ *
18609
+ * @return {Mixed}
18610
+ * - Will be `undefined` if no value exists
18611
+ * - Will be `undefined` if the given value is "none".
18612
+ * - Will be the actual value otherwise.
18613
+ *
18614
+ * @private
18615
+ */
18616
+ function getSelectedOptionValue(el, parser) {
18617
+ var value = el.options[el.options.selectedIndex].value;
18618
+
18619
+ return parseOptionValue(value, parser);
18620
+ }
18621
+
18622
+ /**
18623
+ * Sets the selected <option> element within a <select> element based on a
18624
+ * given value.
18625
+ *
18626
+ * @param {Element} el
18627
+ * The element to look in.
18628
+ *
18629
+ * @param {string} value
18630
+ * the property to look on.
18631
+ *
18632
+ * @param {Function} [parser]
18633
+ * Optional function to adjust the value before comparing.
18634
+ *
18635
+ * @private
18636
+ */
18637
+ function setSelectedOption(el, value, parser) {
18638
+ if (!value) {
18639
+ return;
18640
+ }
18641
+
18642
+ for (var i = 0; i < el.options.length; i++) {
18643
+ if (parseOptionValue(el.options[i].value, parser) === value) {
18644
+ el.selectedIndex = i;
18645
+ break;
18646
+ }
18647
+ }
18648
+ }
18649
+
18650
+ /**
18651
+ * Manipulate Text Tracks settings.
18652
+ *
18653
+ * @extends ModalDialog
18654
+ */
18655
+
18656
+ var TextTrackSettings = function (_ModalDialog) {
18657
+ inherits(TextTrackSettings, _ModalDialog);
18658
+
18659
+ /**
18660
+ * Creates an instance of this class.
18661
+ *
18662
+ * @param {Player} player
18663
+ * The `Player` that this class should be attached to.
18664
+ *
18665
+ * @param {Object} [options]
18666
+ * The key/value store of player options.
18667
+ */
18668
+ function TextTrackSettings(player, options) {
18669
+ classCallCheck(this, TextTrackSettings);
18670
+
18671
+ options.temporary = false;
18672
+
18673
+ var _this = possibleConstructorReturn(this, _ModalDialog.call(this, player, options));
18674
+
18675
+ _this.updateDisplay = bind(_this, _this.updateDisplay);
18676
+
18677
+ // fill the modal and pretend we have opened it
18678
+ _this.fill();
18679
+ _this.hasBeenOpened_ = _this.hasBeenFilled_ = true;
18680
+
18681
+ _this.endDialog = createEl('p', {
18682
+ className: 'vjs-control-text',
18683
+ textContent: _this.localize('End of dialog window.')
18684
+ });
18685
+ _this.el().appendChild(_this.endDialog);
18686
+
18687
+ _this.setDefaults();
18688
+
18689
+ // Grab `persistTextTrackSettings` from the player options if not passed in child options
18690
+ if (options.persistTextTrackSettings === undefined) {
18691
+ _this.options_.persistTextTrackSettings = _this.options_.playerOptions.persistTextTrackSettings;
18692
+ }
18693
+
18694
+ _this.on(_this.$('.vjs-done-button'), 'click', function () {
18695
+ _this.saveSettings();
18696
+ _this.close();
18697
+ });
18698
+
18699
+ _this.on(_this.$('.vjs-default-button'), 'click', function () {
18700
+ _this.setDefaults();
18701
+ _this.updateDisplay();
18702
+ });
18703
+
18704
+ each(selectConfigs, function (config) {
18705
+ _this.on(_this.$(config.selector), 'change', _this.updateDisplay);
18706
+ });
18707
+
18708
+ if (_this.options_.persistTextTrackSettings) {
18709
+ _this.restoreSettings();
18710
+ }
18711
+ return _this;
18712
+ }
18713
+
18714
+ TextTrackSettings.prototype.dispose = function dispose() {
18715
+ this.endDialog = null;
18716
+
18717
+ _ModalDialog.prototype.dispose.call(this);
18718
+ };
18719
+
18720
+ /**
18721
+ * Create a <select> element with configured options.
18722
+ *
18723
+ * @param {string} key
18724
+ * Configuration key to use during creation.
18725
+ *
18726
+ * @return {string}
18727
+ * An HTML string.
18728
+ *
18729
+ * @private
18730
+ */
18731
+
18732
+
18733
+ TextTrackSettings.prototype.createElSelect_ = function createElSelect_(key) {
18734
+ var _this2 = this;
18735
+
18736
+ var legendId = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : '';
18737
+ var type = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 'label';
18738
+
18739
+ var config = selectConfigs[key];
18740
+ var id = config.id.replace('%s', this.id_);
18741
+ var selectLabelledbyIds = [legendId, id].join(' ').trim();
18742
+
18743
+ return ['<' + type + ' id="' + id + '" class="' + (type === 'label' ? 'vjs-label' : '') + '">', this.localize(config.label), '</' + type + '>', '<select aria-labelledby="' + selectLabelledbyIds + '">'].concat(config.options.map(function (o) {
18744
+ var optionId = id + '-' + o[1].replace(/\W+/g, '');
18745
+
18746
+ return ['<option id="' + optionId + '" value="' + o[0] + '" ', 'aria-labelledby="' + selectLabelledbyIds + ' ' + optionId + '">', _this2.localize(o[1]), '</option>'].join('');
18747
+ })).concat('</select>').join('');
18748
+ };
18749
+
18750
+ /**
18751
+ * Create foreground color element for the component
18752
+ *
18753
+ * @return {string}
18754
+ * An HTML string.
18755
+ *
18756
+ * @private
18757
+ */
18758
+
18759
+
18760
+ TextTrackSettings.prototype.createElFgColor_ = function createElFgColor_() {
18761
+ var legendId = 'captions-text-legend-' + this.id_;
18762
+
18763
+ return ['<fieldset class="vjs-fg-color vjs-track-setting">', '<legend id="' + legendId + '">', this.localize('Text'), '</legend>', this.createElSelect_('color', legendId), '<span class="vjs-text-opacity vjs-opacity">', this.createElSelect_('textOpacity', legendId), '</span>', '</fieldset>'].join('');
18764
+ };
18765
+
18766
+ /**
18767
+ * Create background color element for the component
18768
+ *
18769
+ * @return {string}
18770
+ * An HTML string.
18771
+ *
18772
+ * @private
18773
+ */
18774
+
18775
+
18776
+ TextTrackSettings.prototype.createElBgColor_ = function createElBgColor_() {
18777
+ var legendId = 'captions-background-' + this.id_;
18778
+
18779
+ return ['<fieldset class="vjs-bg-color vjs-track-setting">', '<legend id="' + legendId + '">', this.localize('Background'), '</legend>', this.createElSelect_('backgroundColor', legendId), '<span class="vjs-bg-opacity vjs-opacity">', this.createElSelect_('backgroundOpacity', legendId), '</span>', '</fieldset>'].join('');
18780
+ };
18781
+
18782
+ /**
18783
+ * Create window color element for the component
18784
+ *
18785
+ * @return {string}
18786
+ * An HTML string.
18787
+ *
18788
+ * @private
18789
+ */
18790
+
18791
+
18792
+ TextTrackSettings.prototype.createElWinColor_ = function createElWinColor_() {
18793
+ var legendId = 'captions-window-' + this.id_;
18794
+
18795
+ return ['<fieldset class="vjs-window-color vjs-track-setting">', '<legend id="' + legendId + '">', this.localize('Window'), '</legend>', this.createElSelect_('windowColor', legendId), '<span class="vjs-window-opacity vjs-opacity">', this.createElSelect_('windowOpacity', legendId), '</span>', '</fieldset>'].join('');
18796
+ };
18797
+
18798
+ /**
18799
+ * Create color elements for the component
18800
+ *
18801
+ * @return {Element}
18802
+ * The element that was created
18803
+ *
18804
+ * @private
18805
+ */
18806
+
18807
+
18808
+ TextTrackSettings.prototype.createElColors_ = function createElColors_() {
18809
+ return createEl('div', {
18810
+ className: 'vjs-track-settings-colors',
18811
+ innerHTML: [this.createElFgColor_(), this.createElBgColor_(), this.createElWinColor_()].join('')
18812
+ });
18813
+ };
18814
+
18815
+ /**
18816
+ * Create font elements for the component
18817
+ *
18818
+ * @return {Element}
18819
+ * The element that was created.
18820
+ *
18821
+ * @private
18822
+ */
18823
+
18824
+
18825
+ TextTrackSettings.prototype.createElFont_ = function createElFont_() {
18826
+ return createEl('div', {
18827
+ className: 'vjs-track-settings-font',
18828
+ innerHTML: ['<fieldset class="vjs-font-percent vjs-track-setting">', this.createElSelect_('fontPercent', '', 'legend'), '</fieldset>', '<fieldset class="vjs-edge-style vjs-track-setting">', this.createElSelect_('edgeStyle', '', 'legend'), '</fieldset>', '<fieldset class="vjs-font-family vjs-track-setting">', this.createElSelect_('fontFamily', '', 'legend'), '</fieldset>'].join('')
18829
+ });
18830
+ };
18831
+
18832
+ /**
18833
+ * Create controls for the component
18834
+ *
18835
+ * @return {Element}
18836
+ * The element that was created.
18837
+ *
18838
+ * @private
18839
+ */
18840
+
18841
+
18842
+ TextTrackSettings.prototype.createElControls_ = function createElControls_() {
18843
+ var defaultsDescription = this.localize('restore all settings to the default values');
18844
+
18845
+ return createEl('div', {
18846
+ className: 'vjs-track-settings-controls',
18847
+ innerHTML: ['<button class="vjs-default-button" title="' + defaultsDescription + '">', this.localize('Reset'), '<span class="vjs-control-text"> ' + defaultsDescription + '</span>', '</button>', '<button class="vjs-done-button">' + this.localize('Done') + '</button>'].join('')
18848
+ });
18849
+ };
18850
+
18851
+ TextTrackSettings.prototype.content = function content() {
18852
+ return [this.createElColors_(), this.createElFont_(), this.createElControls_()];
18853
+ };
18854
+
18855
+ TextTrackSettings.prototype.label = function label() {
18856
+ return this.localize('Caption Settings Dialog');
18857
+ };
18858
+
18859
+ TextTrackSettings.prototype.description = function description() {
18860
+ return this.localize('Beginning of dialog window. Escape will cancel and close the window.');
18861
+ };
18862
+
18863
+ TextTrackSettings.prototype.buildCSSClass = function buildCSSClass() {
18864
+ return _ModalDialog.prototype.buildCSSClass.call(this) + ' vjs-text-track-settings';
18865
+ };
18866
+
18867
+ /**
18868
+ * Gets an object of text track settings (or null).
18869
+ *
18870
+ * @return {Object}
18871
+ * An object with config values parsed from the DOM or localStorage.
18872
+ */
18873
+
18874
+
18875
+ TextTrackSettings.prototype.getValues = function getValues() {
18876
+ var _this3 = this;
18877
+
18878
+ return reduce(selectConfigs, function (accum, config, key) {
18879
+ var value = getSelectedOptionValue(_this3.$(config.selector), config.parser);
18880
+
18881
+ if (value !== undefined) {
18882
+ accum[key] = value;
18883
+ }
18884
+
18885
+ return accum;
18886
+ }, {});
18887
+ };
18888
+
18889
+ /**
18890
+ * Sets text track settings from an object of values.
18891
+ *
18892
+ * @param {Object} values
18893
+ * An object with config values parsed from the DOM or localStorage.
18894
+ */
18895
+
18896
+
18897
+ TextTrackSettings.prototype.setValues = function setValues(values) {
18898
+ var _this4 = this;
18899
+
18900
+ each(selectConfigs, function (config, key) {
18901
+ setSelectedOption(_this4.$(config.selector), values[key], config.parser);
18902
+ });
18903
+ };
18904
+
18905
+ /**
18906
+ * Sets all `<select>` elements to their default values.
18907
+ */
18908
+
18909
+
18910
+ TextTrackSettings.prototype.setDefaults = function setDefaults() {
18911
+ var _this5 = this;
18912
+
18913
+ each(selectConfigs, function (config) {
18914
+ var index = config.hasOwnProperty('default') ? config.default : 0;
18915
+
18916
+ _this5.$(config.selector).selectedIndex = index;
18917
+ });
18918
+ };
18919
+
18920
+ /**
18921
+ * Restore texttrack settings from localStorage
18922
+ */
18923
+
18924
+
18925
+ TextTrackSettings.prototype.restoreSettings = function restoreSettings() {
18926
+ var values = void 0;
18927
+
18928
+ try {
18929
+ values = JSON.parse(window_1.localStorage.getItem(LOCAL_STORAGE_KEY));
18930
+ } catch (err) {
18931
+ log$1.warn(err);
18932
+ }
18933
+
18934
+ if (values) {
18935
+ this.setValues(values);
18936
+ }
18937
+ };
18938
+
18939
+ /**
18940
+ * Save text track settings to localStorage
18941
+ */
18942
+
18943
+
18944
+ TextTrackSettings.prototype.saveSettings = function saveSettings() {
18945
+ if (!this.options_.persistTextTrackSettings) {
18946
+ return;
18947
+ }
18948
+
18949
+ var values = this.getValues();
18950
+
18951
+ try {
18952
+ if (Object.keys(values).length) {
18953
+ window_1.localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(values));
18954
+ } else {
18955
+ window_1.localStorage.removeItem(LOCAL_STORAGE_KEY);
18956
+ }
18957
+ } catch (err) {
18958
+ log$1.warn(err);
18959
+ }
18960
+ };
18961
+
18962
+ /**
18963
+ * Update display of text track settings
18964
+ */
18965
+
18966
+
18967
+ TextTrackSettings.prototype.updateDisplay = function updateDisplay() {
18968
+ var ttDisplay = this.player_.getChild('textTrackDisplay');
18969
+
18970
+ if (ttDisplay) {
18971
+ ttDisplay.updateDisplay();
18972
+ }
18973
+ };
18974
+
18975
+ /**
18976
+ * conditionally blur the element and refocus the captions button
18977
+ *
18978
+ * @private
18979
+ */
18980
+
18981
+
18982
+ TextTrackSettings.prototype.conditionalBlur_ = function conditionalBlur_() {
18983
+ this.previouslyActiveEl_ = null;
18984
+ this.off(document_1, 'keydown', this.handleKeyDown);
18985
+
18986
+ var cb = this.player_.controlBar;
18987
+ var subsCapsBtn = cb && cb.subsCapsButton;
18988
+ var ccBtn = cb && cb.captionsButton;
18989
+
18990
+ if (subsCapsBtn) {
18991
+ subsCapsBtn.focus();
18992
+ } else if (ccBtn) {
18993
+ ccBtn.focus();
18994
+ }
18995
+ };
18996
+
18997
+ return TextTrackSettings;
18998
+ }(ModalDialog);
18999
+
19000
+ Component.registerComponent('TextTrackSettings', TextTrackSettings);
19001
+
19002
+ /**
19003
+ * @file resize-manager.js
19004
+ */
19005
+
19006
+ /**
19007
+ * A Resize Manager. It is in charge of triggering `playerresize` on the player in the right conditions.
19008
+ *
19009
+ * It'll either create an iframe and use a debounced resize handler on it or use the new {@link https://wicg.github.io/ResizeObserver/|ResizeObserver}.
19010
+ *
19011
+ * If the ResizeObserver is available natively, it will be used. A polyfill can be passed in as an option.
19012
+ * If a `playerresize` event is not needed, the ResizeManager component can be removed from the player, see the example below.
19013
+ * @example <caption>How to disable the resize manager</caption>
19014
+ * const player = videojs('#vid', {
19015
+ * resizeManager: false
19016
+ * });
19017
+ *
19018
+ * @see {@link https://wicg.github.io/ResizeObserver/|ResizeObserver specification}
19019
+ *
19020
+ * @extends Component
19021
+ */
19022
+
19023
+ var ResizeManager = function (_Component) {
19024
+ inherits(ResizeManager, _Component);
19025
+
19026
+ /**
19027
+ * Create the ResizeManager.
19028
+ *
19029
+ * @param {Object} player
19030
+ * The `Player` that this class should be attached to.
19031
+ *
19032
+ * @param {Object} [options]
19033
+ * The key/value store of ResizeManager options.
19034
+ *
19035
+ * @param {Object} [options.ResizeObserver]
19036
+ * A polyfill for ResizeObserver can be passed in here.
19037
+ * If this is set to null it will ignore the native ResizeObserver and fall back to the iframe fallback.
19038
+ */
19039
+ function ResizeManager(player, options) {
19040
+ classCallCheck(this, ResizeManager);
19041
+
19042
+ var RESIZE_OBSERVER_AVAILABLE = options.ResizeObserver || window_1.ResizeObserver;
19043
+
19044
+ // if `null` was passed, we want to disable the ResizeObserver
19045
+ if (options.ResizeObserver === null) {
19046
+ RESIZE_OBSERVER_AVAILABLE = false;
19047
+ }
19048
+
19049
+ // Only create an element when ResizeObserver isn't available
19050
+ var options_ = mergeOptions({ createEl: !RESIZE_OBSERVER_AVAILABLE }, options);
19051
+
19052
+ var _this = possibleConstructorReturn(this, _Component.call(this, player, options_));
19053
+
19054
+ _this.ResizeObserver = options.ResizeObserver || window_1.ResizeObserver;
19055
+ _this.loadListener_ = null;
19056
+ _this.resizeObserver_ = null;
19057
+ _this.debouncedHandler_ = debounce(function () {
19058
+ _this.resizeHandler();
19059
+ }, 100, false, player);
19060
+
19061
+ if (RESIZE_OBSERVER_AVAILABLE) {
19062
+ _this.resizeObserver_ = new _this.ResizeObserver(_this.debouncedHandler_);
19063
+ _this.resizeObserver_.observe(player.el());
19064
+ } else {
19065
+ _this.loadListener_ = function () {
19066
+ if (_this.el_.contentWindow) {
19067
+ on(_this.el_.contentWindow, 'resize', _this.debouncedHandler_);
19068
+ }
19069
+ _this.off('load', _this.loadListener_);
19070
+ };
19071
+
19072
+ _this.on('load', _this.loadListener_);
19073
+ }
19074
+ return _this;
19075
+ }
19076
+
19077
+ ResizeManager.prototype.createEl = function createEl() {
19078
+ return _Component.prototype.createEl.call(this, 'iframe', {
19079
+ className: 'vjs-resize-manager'
19080
+ });
19081
+ };
19082
+
19083
+ /**
19084
+ * Called when a resize is triggered on the iframe or a resize is observed via the ResizeObserver
19085
+ *
19086
+ * @fires Player#playerresize
19087
+ */
19088
+
19089
+
19090
+ ResizeManager.prototype.resizeHandler = function resizeHandler() {
19091
+ /**
19092
+ * Called when the player size has changed
19093
+ *
19094
+ * @event Player#playerresize
19095
+ * @type {EventTarget~Event}
19096
+ */
19097
+ this.player_.trigger('playerresize');
19098
+ };
19099
+
19100
+ ResizeManager.prototype.dispose = function dispose() {
19101
+ if (this.resizeObserver_) {
19102
+ if (this.player_.el()) {
19103
+ this.resizeObserver_.unobserve(this.player_.el());
19104
+ }
19105
+ this.resizeObserver_.disconnect();
19106
+ }
19107
+
19108
+ if (this.el_ && this.el_.contentWindow) {
19109
+ off(this.el_.contentWindow, 'resize', this.debouncedHandler_);
19110
+ }
19111
+
19112
+ if (this.loadListener_) {
19113
+ this.off('load', this.loadListener_);
19114
+ }
19115
+
19116
+ this.ResizeObserver = null;
19117
+ this.resizeObserver = null;
19118
+ this.debouncedHandler_ = null;
19119
+ this.loadListener_ = null;
19120
+ };
19121
+
19122
+ return ResizeManager;
19123
+ }(Component);
19124
+
19125
+ Component.registerComponent('ResizeManager', ResizeManager);
19126
+
19127
+ /**
19128
+ * This function is used to fire a sourceset when there is something
19129
+ * similar to `mediaEl.load()` being called. It will try to find the source via
19130
+ * the `src` attribute and then the `<source>` elements. It will then fire `sourceset`
19131
+ * with the source that was found or empty string if we cannot know. If it cannot
19132
+ * find a source then `sourceset` will not be fired.
19133
+ *
19134
+ * @param {Html5} tech
19135
+ * The tech object that sourceset was setup on
19136
+ *
19137
+ * @return {boolean}
19138
+ * returns false if the sourceset was not fired and true otherwise.
19139
+ */
19140
+ var sourcesetLoad = function sourcesetLoad(tech) {
19141
+ var el = tech.el();
19142
+
19143
+ // if `el.src` is set, that source will be loaded.
19144
+ if (el.hasAttribute('src')) {
19145
+ tech.triggerSourceset(el.src);
19146
+ return true;
19147
+ }
19148
+
19149
+ /**
19150
+ * Since there isn't a src property on the media element, source elements will be used for
19151
+ * implementing the source selection algorithm. This happens asynchronously and
19152
+ * for most cases were there is more than one source we cannot tell what source will
19153
+ * be loaded, without re-implementing the source selection algorithm. At this time we are not
19154
+ * going to do that. There are three special cases that we do handle here though:
19155
+ *
19156
+ * 1. If there are no sources, do not fire `sourceset`.
19157
+ * 2. If there is only one `<source>` with a `src` property/attribute that is our `src`
19158
+ * 3. If there is more than one `<source>` but all of them have the same `src` url.
19159
+ * That will be our src.
19160
+ */
19161
+ var sources = tech.$$('source');
19162
+ var srcUrls = [];
19163
+ var src = '';
19164
+
19165
+ // if there are no sources, do not fire sourceset
19166
+ if (!sources.length) {
19167
+ return false;
19168
+ }
19169
+
19170
+ // only count valid/non-duplicate source elements
19171
+ for (var i = 0; i < sources.length; i++) {
19172
+ var url = sources[i].src;
19173
+
19174
+ if (url && srcUrls.indexOf(url) === -1) {
19175
+ srcUrls.push(url);
19176
+ }
19177
+ }
19178
+
19179
+ // there were no valid sources
19180
+ if (!srcUrls.length) {
19181
+ return false;
19182
+ }
19183
+
19184
+ // there is only one valid source element url
19185
+ // use that
19186
+ if (srcUrls.length === 1) {
19187
+ src = srcUrls[0];
19188
+ }
19189
+
19190
+ tech.triggerSourceset(src);
19191
+ return true;
19192
+ };
19193
+
19194
+ /**
19195
+ * our implementation of an `innerHTML` descriptor for browsers
19196
+ * that do not have one.
19197
+ */
19198
+ var innerHTMLDescriptorPolyfill = Object.defineProperty({}, 'innerHTML', {
19199
+ get: function get() {
19200
+ return this.cloneNode(true).innerHTML;
19201
+ },
19202
+ set: function set(v) {
19203
+ // make a dummy node to use innerHTML on
19204
+ var dummy = document_1.createElement(this.nodeName.toLowerCase());
19205
+
19206
+ // set innerHTML to the value provided
19207
+ dummy.innerHTML = v;
19208
+
19209
+ // make a document fragment to hold the nodes from dummy
19210
+ var docFrag = document_1.createDocumentFragment();
19211
+
19212
+ // copy all of the nodes created by the innerHTML on dummy
19213
+ // to the document fragment
19214
+ while (dummy.childNodes.length) {
19215
+ docFrag.appendChild(dummy.childNodes[0]);
19216
+ }
19217
+
19218
+ // remove content
19219
+ this.innerText = '';
19220
+
19221
+ // now we add all of that html in one by appending the
19222
+ // document fragment. This is how innerHTML does it.
19223
+ window_1.Element.prototype.appendChild.call(this, docFrag);
19224
+
19225
+ // then return the result that innerHTML's setter would
19226
+ return this.innerHTML;
19227
+ }
19228
+ });
19229
+
19230
+ /**
19231
+ * Get a property descriptor given a list of priorities and the
19232
+ * property to get.
19233
+ */
19234
+ var getDescriptor = function getDescriptor(priority, prop) {
19235
+ var descriptor = {};
19236
+
19237
+ for (var i = 0; i < priority.length; i++) {
19238
+ descriptor = Object.getOwnPropertyDescriptor(priority[i], prop);
19239
+
19240
+ if (descriptor && descriptor.set && descriptor.get) {
19241
+ break;
19242
+ }
19243
+ }
19244
+
19245
+ descriptor.enumerable = true;
19246
+ descriptor.configurable = true;
19247
+
19248
+ return descriptor;
19249
+ };
19250
+
19251
+ var getInnerHTMLDescriptor = function getInnerHTMLDescriptor(tech) {
19252
+ return getDescriptor([tech.el(), window_1.HTMLMediaElement.prototype, window_1.Element.prototype, innerHTMLDescriptorPolyfill], 'innerHTML');
19253
+ };
19254
+
19255
+ /**
19256
+ * Patches browser internal functions so that we can tell synchronously
19257
+ * if a `<source>` was appended to the media element. For some reason this
19258
+ * causes a `sourceset` if the the media element is ready and has no source.
19259
+ * This happens when:
19260
+ * - The page has just loaded and the media element does not have a source.
19261
+ * - The media element was emptied of all sources, then `load()` was called.
19262
+ *
19263
+ * It does this by patching the following functions/properties when they are supported:
19264
+ *
19265
+ * - `append()` - can be used to add a `<source>` element to the media element
19266
+ * - `appendChild()` - can be used to add a `<source>` element to the media element
19267
+ * - `insertAdjacentHTML()` - can be used to add a `<source>` element to the media element
19268
+ * - `innerHTML` - can be used to add a `<source>` element to the media element
19269
+ *
19270
+ * @param {Html5} tech
19271
+ * The tech object that sourceset is being setup on.
19272
+ */
19273
+ var firstSourceWatch = function firstSourceWatch(tech) {
19274
+ var el = tech.el();
19275
+
19276
+ // make sure firstSourceWatch isn't setup twice.
19277
+ if (el.resetSourceWatch_) {
19278
+ return;
19279
+ }
19280
+
19281
+ var old = {};
19282
+ var innerDescriptor = getInnerHTMLDescriptor(tech);
19283
+ var appendWrapper = function appendWrapper(appendFn) {
19284
+ return function () {
19285
+ for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
19286
+ args[_key] = arguments[_key];
19287
+ }
19288
+
19289
+ var retval = appendFn.apply(el, args);
19290
+
19291
+ sourcesetLoad(tech);
19292
+
19293
+ return retval;
19294
+ };
19295
+ };
19296
+
19297
+ ['append', 'appendChild', 'insertAdjacentHTML'].forEach(function (k) {
19298
+ if (!el[k]) {
19299
+ return;
19300
+ }
19301
+
19302
+ // store the old function
19303
+ old[k] = el[k];
19304
+
19305
+ // call the old function with a sourceset if a source
19306
+ // was loaded
19307
+ el[k] = appendWrapper(old[k]);
19308
+ });
19309
+
19310
+ Object.defineProperty(el, 'innerHTML', mergeOptions(innerDescriptor, {
19311
+ set: appendWrapper(innerDescriptor.set)
19312
+ }));
19313
+
19314
+ el.resetSourceWatch_ = function () {
19315
+ el.resetSourceWatch_ = null;
19316
+ Object.keys(old).forEach(function (k) {
19317
+ el[k] = old[k];
19318
+ });
19319
+
19320
+ Object.defineProperty(el, 'innerHTML', innerDescriptor);
19321
+ };
19322
+
19323
+ // on the first sourceset, we need to revert our changes
19324
+ tech.one('sourceset', el.resetSourceWatch_);
19325
+ };
19326
+
19327
+ /**
19328
+ * our implementation of a `src` descriptor for browsers
19329
+ * that do not have one.
19330
+ */
19331
+ var srcDescriptorPolyfill = Object.defineProperty({}, 'src', {
19332
+ get: function get() {
19333
+ if (this.hasAttribute('src')) {
19334
+ return getAbsoluteURL(window_1.Element.prototype.getAttribute.call(this, 'src'));
19335
+ }
19336
+
19337
+ return '';
19338
+ },
19339
+ set: function set(v) {
19340
+ window_1.Element.prototype.setAttribute.call(this, 'src', v);
19341
+
19342
+ return v;
19343
+ }
19344
+ });
19345
+
19346
+ var getSrcDescriptor = function getSrcDescriptor(tech) {
19347
+ return getDescriptor([tech.el(), window_1.HTMLMediaElement.prototype, srcDescriptorPolyfill], 'src');
19348
+ };
19349
+
19350
+ /**
19351
+ * setup `sourceset` handling on the `Html5` tech. This function
19352
+ * patches the following element properties/functions:
19353
+ *
19354
+ * - `src` - to determine when `src` is set
19355
+ * - `setAttribute()` - to determine when `src` is set
19356
+ * - `load()` - this re-triggers the source selection algorithm, and can
19357
+ * cause a sourceset.
19358
+ *
19359
+ * If there is no source when we are adding `sourceset` support or during a `load()`
19360
+ * we also patch the functions listed in `firstSourceWatch`.
19361
+ *
19362
+ * @param {Html5} tech
19363
+ * The tech to patch
19364
+ */
19365
+ var setupSourceset = function setupSourceset(tech) {
19366
+ if (!tech.featuresSourceset) {
19367
+ return;
19368
+ }
19369
+
19370
+ var el = tech.el();
19371
+
19372
+ // make sure sourceset isn't setup twice.
19373
+ if (el.resetSourceset_) {
19374
+ return;
19375
+ }
19376
+
19377
+ var srcDescriptor = getSrcDescriptor(tech);
19378
+ var oldSetAttribute = el.setAttribute;
19379
+ var oldLoad = el.load;
19380
+
19381
+ Object.defineProperty(el, 'src', mergeOptions(srcDescriptor, {
19382
+ set: function set(v) {
19383
+ var retval = srcDescriptor.set.call(el, v);
19384
+
19385
+ // we use the getter here to get the actual value set on src
19386
+ tech.triggerSourceset(el.src);
19387
+
19388
+ return retval;
19389
+ }
19390
+ }));
19391
+
19392
+ el.setAttribute = function (n, v) {
19393
+ var retval = oldSetAttribute.call(el, n, v);
19394
+
19395
+ if (/src/i.test(n)) {
19396
+ tech.triggerSourceset(el.src);
19397
+ }
19398
+
19399
+ return retval;
19400
+ };
19401
+
19402
+ el.load = function () {
19403
+ var retval = oldLoad.call(el);
19404
+
19405
+ // if load was called, but there was no source to fire
19406
+ // sourceset on. We have to watch for a source append
19407
+ // as that can trigger a `sourceset` when the media element
19408
+ // has no source
19409
+ if (!sourcesetLoad(tech)) {
19410
+ tech.triggerSourceset('');
19411
+ firstSourceWatch(tech);
19412
+ }
19413
+
19414
+ return retval;
19415
+ };
19416
+
19417
+ if (el.currentSrc) {
19418
+ tech.triggerSourceset(el.currentSrc);
19419
+ } else if (!sourcesetLoad(tech)) {
19420
+ firstSourceWatch(tech);
19421
+ }
19422
+
19423
+ el.resetSourceset_ = function () {
19424
+ el.resetSourceset_ = null;
19425
+ el.load = oldLoad;
19426
+ el.setAttribute = oldSetAttribute;
19427
+ Object.defineProperty(el, 'src', srcDescriptor);
19428
+ if (el.resetSourceWatch_) {
19429
+ el.resetSourceWatch_();
19430
+ }
19431
+ };
19432
+ };
19433
+
19434
+ var _templateObject$1 = taggedTemplateLiteralLoose(['Text Tracks are being loaded from another origin but the crossorigin attribute isn\'t used.\n This may prevent text tracks from loading.'], ['Text Tracks are being loaded from another origin but the crossorigin attribute isn\'t used.\n This may prevent text tracks from loading.']);
19435
+
19436
+ /**
19437
+ * HTML5 Media Controller - Wrapper for HTML5 Media API
19438
+ *
19439
+ * @mixes Tech~SourceHandlerAdditions
19440
+ * @extends Tech
19441
+ */
19442
+
19443
+ var Html5 = function (_Tech) {
19444
+ inherits(Html5, _Tech);
19445
+
19446
+ /**
19447
+ * Create an instance of this Tech.
19448
+ *
19449
+ * @param {Object} [options]
19450
+ * The key/value store of player options.
19451
+ *
19452
+ * @param {Component~ReadyCallback} ready
19453
+ * Callback function to call when the `HTML5` Tech is ready.
19454
+ */
19455
+ function Html5(options, ready) {
19456
+ classCallCheck(this, Html5);
19457
+
19458
+ var _this = possibleConstructorReturn(this, _Tech.call(this, options, ready));
19459
+
19460
+ var source = options.source;
19461
+ var crossoriginTracks = false;
19462
+
19463
+ // Set the source if one is provided
19464
+ // 1) Check if the source is new (if not, we want to keep the original so playback isn't interrupted)
19465
+ // 2) Check to see if the network state of the tag was failed at init, and if so, reset the source
19466
+ // anyway so the error gets fired.
19467
+ if (source && (_this.el_.currentSrc !== source.src || options.tag && options.tag.initNetworkState_ === 3)) {
19468
+ _this.setSource(source);
19469
+ } else {
19470
+ _this.handleLateInit_(_this.el_);
19471
+ }
19472
+
19473
+ // setup sourceset after late sourceset/init
19474
+ if (options.enableSourceset) {
19475
+ _this.setupSourcesetHandling_();
19476
+ }
19477
+
19478
+ if (_this.el_.hasChildNodes()) {
19479
+
19480
+ var nodes = _this.el_.childNodes;
19481
+ var nodesLength = nodes.length;
19482
+ var removeNodes = [];
19483
+
19484
+ while (nodesLength--) {
19485
+ var node = nodes[nodesLength];
19486
+ var nodeName = node.nodeName.toLowerCase();
19487
+
19488
+ if (nodeName === 'track') {
19489
+ if (!_this.featuresNativeTextTracks) {
19490
+ // Empty video tag tracks so the built-in player doesn't use them also.
19491
+ // This may not be fast enough to stop HTML5 browsers from reading the tags
19492
+ // so we'll need to turn off any default tracks if we're manually doing
19493
+ // captions and subtitles. videoElement.textTracks
19494
+ removeNodes.push(node);
19495
+ } else {
19496
+ // store HTMLTrackElement and TextTrack to remote list
19497
+ _this.remoteTextTrackEls().addTrackElement_(node);
19498
+ _this.remoteTextTracks().addTrack(node.track);
19499
+ _this.textTracks().addTrack(node.track);
19500
+ if (!crossoriginTracks && !_this.el_.hasAttribute('crossorigin') && isCrossOrigin(node.src)) {
19501
+ crossoriginTracks = true;
19502
+ }
19503
+ }
19504
+ }
19505
+ }
19506
+
19507
+ for (var i = 0; i < removeNodes.length; i++) {
19508
+ _this.el_.removeChild(removeNodes[i]);
19509
+ }
19510
+ }
19511
+
19512
+ _this.proxyNativeTracks_();
19513
+ if (_this.featuresNativeTextTracks && crossoriginTracks) {
19514
+ log$1.warn(tsml(_templateObject$1));
19515
+ }
19516
+
19517
+ // prevent iOS Safari from disabling metadata text tracks during native playback
19518
+ _this.restoreMetadataTracksInIOSNativePlayer_();
19519
+
19520
+ // Determine if native controls should be used
19521
+ // Our goal should be to get the custom controls on mobile solid everywhere
19522
+ // so we can remove this all together. Right now this will block custom
19523
+ // controls on touch enabled laptops like the Chrome Pixel
19524
+ if ((TOUCH_ENABLED || IS_IPHONE || IS_NATIVE_ANDROID) && options.nativeControlsForTouch === true) {
19525
+ _this.setControls(true);
19526
+ }
19527
+
19528
+ // on iOS, we want to proxy `webkitbeginfullscreen` and `webkitendfullscreen`
19529
+ // into a `fullscreenchange` event
19530
+ _this.proxyWebkitFullscreen_();
19531
+
19532
+ _this.triggerReady();
19533
+ return _this;
19534
+ }
19535
+
19536
+ /**
19537
+ * Dispose of `HTML5` media element and remove all tracks.
19538
+ */
19539
+
19540
+
19541
+ Html5.prototype.dispose = function dispose() {
19542
+ if (this.el_ && this.el_.resetSourceset_) {
19543
+ this.el_.resetSourceset_();
19544
+ }
19545
+ Html5.disposeMediaElement(this.el_);
19546
+ this.options_ = null;
19547
+
19548
+ // tech will handle clearing of the emulated track list
19549
+ _Tech.prototype.dispose.call(this);
19550
+ };
19551
+
19552
+ /**
19553
+ * Modify the media element so that we can detect when
19554
+ * the source is changed. Fires `sourceset` just after the source has changed
19555
+ */
19556
+
19557
+
19558
+ Html5.prototype.setupSourcesetHandling_ = function setupSourcesetHandling_() {
19559
+ setupSourceset(this);
19560
+ };
19561
+
19562
+ /**
19563
+ * When a captions track is enabled in the iOS Safari native player, all other
19564
+ * tracks are disabled (including metadata tracks), which nulls all of their
19565
+ * associated cue points. This will restore metadata tracks to their pre-fullscreen
19566
+ * state in those cases so that cue points are not needlessly lost.
19567
+ *
19568
+ * @private
19569
+ */
19570
+
19571
+
19572
+ Html5.prototype.restoreMetadataTracksInIOSNativePlayer_ = function restoreMetadataTracksInIOSNativePlayer_() {
19573
+ var textTracks = this.textTracks();
19574
+ var metadataTracksPreFullscreenState = void 0;
19575
+
19576
+ // captures a snapshot of every metadata track's current state
19577
+ var takeMetadataTrackSnapshot = function takeMetadataTrackSnapshot() {
19578
+ metadataTracksPreFullscreenState = [];
19579
+
19580
+ for (var i = 0; i < textTracks.length; i++) {
19581
+ var track = textTracks[i];
19582
+
19583
+ if (track.kind === 'metadata') {
19584
+ metadataTracksPreFullscreenState.push({
19585
+ track: track,
19586
+ storedMode: track.mode
19587
+ });
19588
+ }
19589
+ }
19590
+ };
19591
+
19592
+ // snapshot each metadata track's initial state, and update the snapshot
19593
+ // each time there is a track 'change' event
19594
+ takeMetadataTrackSnapshot();
19595
+ textTracks.addEventListener('change', takeMetadataTrackSnapshot);
19596
+
19597
+ this.on('dispose', function () {
19598
+ return textTracks.removeEventListener('change', takeMetadataTrackSnapshot);
19599
+ });
19600
+
19601
+ var restoreTrackMode = function restoreTrackMode() {
19602
+ for (var i = 0; i < metadataTracksPreFullscreenState.length; i++) {
19603
+ var storedTrack = metadataTracksPreFullscreenState[i];
19604
+
19605
+ if (storedTrack.track.mode === 'disabled' && storedTrack.track.mode !== storedTrack.storedMode) {
19606
+ storedTrack.track.mode = storedTrack.storedMode;
19607
+ }
19608
+ }
19609
+ // we only want this handler to be executed on the first 'change' event
19610
+ textTracks.removeEventListener('change', restoreTrackMode);
19611
+ };
19612
+
19613
+ // when we enter fullscreen playback, stop updating the snapshot and
19614
+ // restore all track modes to their pre-fullscreen state
19615
+ this.on('webkitbeginfullscreen', function () {
19616
+ textTracks.removeEventListener('change', takeMetadataTrackSnapshot);
19617
+
19618
+ // remove the listener before adding it just in case it wasn't previously removed
19619
+ textTracks.removeEventListener('change', restoreTrackMode);
19620
+ textTracks.addEventListener('change', restoreTrackMode);
19621
+ });
19622
+
19623
+ // start updating the snapshot again after leaving fullscreen
19624
+ this.on('webkitendfullscreen', function () {
19625
+ // remove the listener before adding it just in case it wasn't previously removed
19626
+ textTracks.removeEventListener('change', takeMetadataTrackSnapshot);
19627
+ textTracks.addEventListener('change', takeMetadataTrackSnapshot);
19628
+
19629
+ // remove the restoreTrackMode handler in case it wasn't triggered during fullscreen playback
19630
+ textTracks.removeEventListener('change', restoreTrackMode);
19631
+ });
19632
+ };
19633
+
19634
+ /**
19635
+ * Attempt to force override of tracks for the given type
19636
+ *
19637
+ * @param {String} type - Track type to override, possible values include 'Audio',
19638
+ * 'Video', and 'Text'.
19639
+ * @param {Boolean} override - If set to true native audio/video will be overridden,
19640
+ * otherwise native audio/video will potentially be used.
19641
+ * @private
19642
+ */
19643
+
19644
+
19645
+ Html5.prototype.overrideNative_ = function overrideNative_(type, override) {
19646
+ var _this2 = this;
19647
+
19648
+ // If there is no behavioral change don't add/remove listeners
19649
+ if (override !== this['featuresNative' + type + 'Tracks']) {
19650
+ return;
19651
+ }
19652
+
19653
+ var lowerCaseType = type.toLowerCase();
19654
+
19655
+ if (this[lowerCaseType + 'TracksListeners_']) {
19656
+ Object.keys(this[lowerCaseType + 'TracksListeners_']).forEach(function (eventName) {
19657
+ var elTracks = _this2.el()[lowerCaseType + 'Tracks'];
19658
+
19659
+ elTracks.removeEventListener(eventName, _this2[lowerCaseType + 'TracksListeners_'][eventName]);
19660
+ });
19661
+ }
19662
+
19663
+ this['featuresNative' + type + 'Tracks'] = !override;
19664
+ this[lowerCaseType + 'TracksListeners_'] = null;
19665
+
19666
+ this.proxyNativeTracksForType_(lowerCaseType);
19667
+ };
19668
+
19669
+ /**
19670
+ * Attempt to force override of native audio tracks.
19671
+ *
19672
+ * @param {Boolean} override - If set to true native audio will be overridden,
19673
+ * otherwise native audio will potentially be used.
19674
+ */
19675
+
19676
+
19677
+ Html5.prototype.overrideNativeAudioTracks = function overrideNativeAudioTracks(override) {
19678
+ this.overrideNative_('Audio', override);
19679
+ };
19680
+
19681
+ /**
19682
+ * Attempt to force override of native video tracks.
19683
+ *
19684
+ * @param {Boolean} override - If set to true native video will be overridden,
19685
+ * otherwise native video will potentially be used.
19686
+ */
19687
+
19688
+
19689
+ Html5.prototype.overrideNativeVideoTracks = function overrideNativeVideoTracks(override) {
19690
+ this.overrideNative_('Video', override);
19691
+ };
19692
+
19693
+ /**
19694
+ * Proxy native track list events for the given type to our track
19695
+ * lists if the browser we are playing in supports that type of track list.
19696
+ *
19697
+ * @param {string} name - Track type; values include 'audio', 'video', and 'text'
19698
+ * @private
19699
+ */
19700
+
19701
+
19702
+ Html5.prototype.proxyNativeTracksForType_ = function proxyNativeTracksForType_(name) {
19703
+ var _this3 = this;
19704
+
19705
+ var props = NORMAL[name];
19706
+ var elTracks = this.el()[props.getterName];
19707
+ var techTracks = this[props.getterName]();
19708
+
19709
+ if (!this['featuresNative' + props.capitalName + 'Tracks'] || !elTracks || !elTracks.addEventListener) {
19710
+ return;
19711
+ }
19712
+ var listeners = {
19713
+ change: function change(e) {
19714
+ techTracks.trigger({
19715
+ type: 'change',
19716
+ target: techTracks,
19717
+ currentTarget: techTracks,
19718
+ srcElement: techTracks
19719
+ });
19720
+ },
19721
+ addtrack: function addtrack(e) {
19722
+ techTracks.addTrack(e.track);
19723
+ },
19724
+ removetrack: function removetrack(e) {
19725
+ techTracks.removeTrack(e.track);
19726
+ }
19727
+ };
19728
+ var removeOldTracks = function removeOldTracks() {
19729
+ var removeTracks = [];
19730
+
19731
+ for (var i = 0; i < techTracks.length; i++) {
19732
+ var found = false;
19733
+
19734
+ for (var j = 0; j < elTracks.length; j++) {
19735
+ if (elTracks[j] === techTracks[i]) {
19736
+ found = true;
19737
+ break;
19738
+ }
19739
+ }
19740
+
19741
+ if (!found) {
19742
+ removeTracks.push(techTracks[i]);
19743
+ }
19744
+ }
19745
+
19746
+ while (removeTracks.length) {
19747
+ techTracks.removeTrack(removeTracks.shift());
19748
+ }
19749
+ };
19750
+
19751
+ this[props.getterName + 'Listeners_'] = listeners;
19752
+
19753
+ Object.keys(listeners).forEach(function (eventName) {
19754
+ var listener = listeners[eventName];
19755
+
19756
+ elTracks.addEventListener(eventName, listener);
19757
+ _this3.on('dispose', function (e) {
19758
+ return elTracks.removeEventListener(eventName, listener);
19759
+ });
19760
+ });
19761
+
19762
+ // Remove (native) tracks that are not used anymore
19763
+ this.on('loadstart', removeOldTracks);
19764
+ this.on('dispose', function (e) {
19765
+ return _this3.off('loadstart', removeOldTracks);
19766
+ });
19767
+ };
19768
+
19769
+ /**
19770
+ * Proxy all native track list events to our track lists if the browser we are playing
19771
+ * in supports that type of track list.
19772
+ *
19773
+ * @private
19774
+ */
19775
+
19776
+
19777
+ Html5.prototype.proxyNativeTracks_ = function proxyNativeTracks_() {
19778
+ var _this4 = this;
19779
+
19780
+ NORMAL.names.forEach(function (name) {
19781
+ _this4.proxyNativeTracksForType_(name);
19782
+ });
19783
+ };
19784
+
19785
+ /**
19786
+ * Create the `Html5` Tech's DOM element.
19787
+ *
19788
+ * @return {Element}
19789
+ * The element that gets created.
19790
+ */
19791
+
19792
+
19793
+ Html5.prototype.createEl = function createEl$$1() {
19794
+ var el = this.options_.tag;
19795
+
19796
+ // Check if this browser supports moving the element into the box.
19797
+ // On the iPhone video will break if you move the element,
19798
+ // So we have to create a brand new element.
19799
+ // If we ingested the player div, we do not need to move the media element.
19800
+ if (!el || !(this.options_.playerElIngest || this.movingMediaElementInDOM)) {
19801
+
19802
+ // If the original tag is still there, clone and remove it.
19803
+ if (el) {
19804
+ var clone = el.cloneNode(true);
19805
+
19806
+ if (el.parentNode) {
19807
+ el.parentNode.insertBefore(clone, el);
19808
+ }
19809
+ Html5.disposeMediaElement(el);
19810
+ el = clone;
19811
+ } else {
19812
+ el = document_1.createElement('video');
19813
+
19814
+ // determine if native controls should be used
19815
+ var tagAttributes = this.options_.tag && getAttributes(this.options_.tag);
19816
+ var attributes = mergeOptions({}, tagAttributes);
19817
+
19818
+ if (!TOUCH_ENABLED || this.options_.nativeControlsForTouch !== true) {
19819
+ delete attributes.controls;
19820
+ }
19821
+
19822
+ setAttributes(el, assign(attributes, {
19823
+ id: this.options_.techId,
19824
+ class: 'vjs-tech'
19825
+ }));
19826
+ }
19827
+
19828
+ el.playerId = this.options_.playerId;
19829
+ }
19830
+
19831
+ if (typeof this.options_.preload !== 'undefined') {
19832
+ setAttribute(el, 'preload', this.options_.preload);
19833
+ }
19834
+
19835
+ // Update specific tag settings, in case they were overridden
19836
+ // `autoplay` has to be *last* so that `muted` and `playsinline` are present
19837
+ // when iOS/Safari or other browsers attempt to autoplay.
19838
+ var settingsAttrs = ['loop', 'muted', 'playsinline', 'autoplay'];
19839
+
19840
+ for (var i = 0; i < settingsAttrs.length; i++) {
19841
+ var attr = settingsAttrs[i];
19842
+ var value = this.options_[attr];
19843
+
19844
+ if (typeof value !== 'undefined') {
19845
+ if (value) {
19846
+ setAttribute(el, attr, attr);
19847
+ } else {
19848
+ removeAttribute(el, attr);
19849
+ }
19850
+ el[attr] = value;
19851
+ }
19852
+ }
19853
+
19854
+ return el;
19855
+ };
19856
+
19857
+ /**
19858
+ * This will be triggered if the loadstart event has already fired, before videojs was
19859
+ * ready. Two known examples of when this can happen are:
19860
+ * 1. If we're loading the playback object after it has started loading
19861
+ * 2. The media is already playing the (often with autoplay on) then
19862
+ *
19863
+ * This function will fire another loadstart so that videojs can catchup.
19864
+ *
19865
+ * @fires Tech#loadstart
19866
+ *
19867
+ * @return {undefined}
19868
+ * returns nothing.
19869
+ */
19870
+
19871
+
19872
+ Html5.prototype.handleLateInit_ = function handleLateInit_(el) {
19873
+ if (el.networkState === 0 || el.networkState === 3) {
19874
+ // The video element hasn't started loading the source yet
19875
+ // or didn't find a source
19876
+ return;
19877
+ }
19878
+
19879
+ if (el.readyState === 0) {
19880
+ // NetworkState is set synchronously BUT loadstart is fired at the
19881
+ // end of the current stack, usually before setInterval(fn, 0).
19882
+ // So at this point we know loadstart may have already fired or is
19883
+ // about to fire, and either way the player hasn't seen it yet.
19884
+ // We don't want to fire loadstart prematurely here and cause a
19885
+ // double loadstart so we'll wait and see if it happens between now
19886
+ // and the next loop, and fire it if not.
19887
+ // HOWEVER, we also want to make sure it fires before loadedmetadata
19888
+ // which could also happen between now and the next loop, so we'll
19889
+ // watch for that also.
19890
+ var loadstartFired = false;
19891
+ var setLoadstartFired = function setLoadstartFired() {
19892
+ loadstartFired = true;
19893
+ };
19894
+
19895
+ this.on('loadstart', setLoadstartFired);
19896
+
19897
+ var triggerLoadstart = function triggerLoadstart() {
19898
+ // We did miss the original loadstart. Make sure the player
19899
+ // sees loadstart before loadedmetadata
19900
+ if (!loadstartFired) {
19901
+ this.trigger('loadstart');
19902
+ }
19903
+ };
19904
+
19905
+ this.on('loadedmetadata', triggerLoadstart);
19906
+
19907
+ this.ready(function () {
19908
+ this.off('loadstart', setLoadstartFired);
19909
+ this.off('loadedmetadata', triggerLoadstart);
19910
+
19911
+ if (!loadstartFired) {
19912
+ // We did miss the original native loadstart. Fire it now.
19913
+ this.trigger('loadstart');
19914
+ }
19915
+ });
19916
+
19917
+ return;
19918
+ }
19919
+
19920
+ // From here on we know that loadstart already fired and we missed it.
19921
+ // The other readyState events aren't as much of a problem if we double
19922
+ // them, so not going to go to as much trouble as loadstart to prevent
19923
+ // that unless we find reason to.
19924
+ var eventsToTrigger = ['loadstart'];
19925
+
19926
+ // loadedmetadata: newly equal to HAVE_METADATA (1) or greater
19927
+ eventsToTrigger.push('loadedmetadata');
19928
+
19929
+ // loadeddata: newly increased to HAVE_CURRENT_DATA (2) or greater
19930
+ if (el.readyState >= 2) {
19931
+ eventsToTrigger.push('loadeddata');
19932
+ }
19933
+
19934
+ // canplay: newly increased to HAVE_FUTURE_DATA (3) or greater
19935
+ if (el.readyState >= 3) {
19936
+ eventsToTrigger.push('canplay');
19937
+ }
19938
+
19939
+ // canplaythrough: newly equal to HAVE_ENOUGH_DATA (4)
19940
+ if (el.readyState >= 4) {
19941
+ eventsToTrigger.push('canplaythrough');
19942
+ }
19943
+
19944
+ // We still need to give the player time to add event listeners
19945
+ this.ready(function () {
19946
+ eventsToTrigger.forEach(function (type) {
19947
+ this.trigger(type);
19948
+ }, this);
19949
+ });
19950
+ };
19951
+
19952
+ /**
19953
+ * Set current time for the `HTML5` tech.
19954
+ *
19955
+ * @param {number} seconds
19956
+ * Set the current time of the media to this.
19957
+ */
19958
+
19959
+
19960
+ Html5.prototype.setCurrentTime = function setCurrentTime(seconds) {
19961
+ try {
19962
+ this.el_.currentTime = seconds;
19963
+ } catch (e) {
19964
+ log$1(e, 'Video is not ready. (Video.js)');
19965
+ // this.warning(VideoJS.warnings.videoNotReady);
19966
+ }
19967
+ };
19968
+
19969
+ /**
19970
+ * Get the current duration of the HTML5 media element.
19971
+ *
19972
+ * @return {number}
19973
+ * The duration of the media or 0 if there is no duration.
19974
+ */
19975
+
19976
+
19977
+ Html5.prototype.duration = function duration() {
19978
+ var _this5 = this;
19979
+
19980
+ // Android Chrome will report duration as Infinity for VOD HLS until after
19981
+ // playback has started, which triggers the live display erroneously.
19982
+ // Return NaN if playback has not started and trigger a durationupdate once
19983
+ // the duration can be reliably known.
19984
+ if (this.el_.duration === Infinity && IS_ANDROID && IS_CHROME && this.el_.currentTime === 0) {
19985
+ // Wait for the first `timeupdate` with currentTime > 0 - there may be
19986
+ // several with 0
19987
+ var checkProgress = function checkProgress() {
19988
+ if (_this5.el_.currentTime > 0) {
19989
+ // Trigger durationchange for genuinely live video
19990
+ if (_this5.el_.duration === Infinity) {
19991
+ _this5.trigger('durationchange');
19992
+ }
19993
+ _this5.off('timeupdate', checkProgress);
19994
+ }
19995
+ };
19996
+
19997
+ this.on('timeupdate', checkProgress);
19998
+ return NaN;
19999
+ }
20000
+ return this.el_.duration || NaN;
20001
+ };
20002
+
20003
+ /**
20004
+ * Get the current width of the HTML5 media element.
20005
+ *
20006
+ * @return {number}
20007
+ * The width of the HTML5 media element.
20008
+ */
20009
+
20010
+
20011
+ Html5.prototype.width = function width() {
20012
+ return this.el_.offsetWidth;
20013
+ };
20014
+
20015
+ /**
20016
+ * Get the current height of the HTML5 media element.
20017
+ *
20018
+ * @return {number}
20019
+ * The height of the HTML5 media element.
20020
+ */
20021
+
20022
+
20023
+ Html5.prototype.height = function height() {
20024
+ return this.el_.offsetHeight;
20025
+ };
20026
+
20027
+ /**
20028
+ * Proxy iOS `webkitbeginfullscreen` and `webkitendfullscreen` into
20029
+ * `fullscreenchange` event.
20030
+ *
20031
+ * @private
20032
+ * @fires fullscreenchange
20033
+ * @listens webkitendfullscreen
20034
+ * @listens webkitbeginfullscreen
20035
+ * @listens webkitbeginfullscreen
20036
+ */
20037
+
20038
+
20039
+ Html5.prototype.proxyWebkitFullscreen_ = function proxyWebkitFullscreen_() {
20040
+ var _this6 = this;
20041
+
20042
+ if (!('webkitDisplayingFullscreen' in this.el_)) {
20043
+ return;
20044
+ }
20045
+
20046
+ var endFn = function endFn() {
20047
+ this.trigger('fullscreenchange', { isFullscreen: false });
20048
+ };
20049
+
20050
+ var beginFn = function beginFn() {
20051
+ if ('webkitPresentationMode' in this.el_ && this.el_.webkitPresentationMode !== 'picture-in-picture') {
20052
+ this.one('webkitendfullscreen', endFn);
20053
+
20054
+ this.trigger('fullscreenchange', { isFullscreen: true });
20055
+ }
20056
+ };
20057
+
20058
+ this.on('webkitbeginfullscreen', beginFn);
20059
+ this.on('dispose', function () {
20060
+ _this6.off('webkitbeginfullscreen', beginFn);
20061
+ _this6.off('webkitendfullscreen', endFn);
20062
+ });
20063
+ };
20064
+
20065
+ /**
20066
+ * Check if fullscreen is supported on the current playback device.
20067
+ *
20068
+ * @return {boolean}
20069
+ * - True if fullscreen is supported.
20070
+ * - False if fullscreen is not supported.
20071
+ */
20072
+
20073
+
20074
+ Html5.prototype.supportsFullScreen = function supportsFullScreen() {
20075
+ if (typeof this.el_.webkitEnterFullScreen === 'function') {
20076
+ var userAgent = window_1.navigator && window_1.navigator.userAgent || '';
20077
+
20078
+ // Seems to be broken in Chromium/Chrome && Safari in Leopard
20079
+ if (/Android/.test(userAgent) || !/Chrome|Mac OS X 10.5/.test(userAgent)) {
20080
+ return true;
20081
+ }
20082
+ }
20083
+ return false;
20084
+ };
20085
+
20086
+ /**
20087
+ * Request that the `HTML5` Tech enter fullscreen.
20088
+ */
20089
+
20090
+
20091
+ Html5.prototype.enterFullScreen = function enterFullScreen() {
20092
+ var video = this.el_;
20093
+
20094
+ if (video.paused && video.networkState <= video.HAVE_METADATA) {
20095
+ // attempt to prime the video element for programmatic access
20096
+ // this isn't necessary on the desktop but shouldn't hurt
20097
+ this.el_.play();
20098
+
20099
+ // playing and pausing synchronously during the transition to fullscreen
20100
+ // can get iOS ~6.1 devices into a play/pause loop
20101
+ this.setTimeout(function () {
20102
+ video.pause();
20103
+ video.webkitEnterFullScreen();
20104
+ }, 0);
20105
+ } else {
20106
+ video.webkitEnterFullScreen();
20107
+ }
20108
+ };
20109
+
20110
+ /**
20111
+ * Request that the `HTML5` Tech exit fullscreen.
20112
+ */
20113
+
20114
+
20115
+ Html5.prototype.exitFullScreen = function exitFullScreen() {
20116
+ this.el_.webkitExitFullScreen();
20117
+ };
20118
+
20119
+ /**
20120
+ * A getter/setter for the `Html5` Tech's source object.
20121
+ * > Note: Please use {@link Html5#setSource}
20122
+ *
20123
+ * @param {Tech~SourceObject} [src]
20124
+ * The source object you want to set on the `HTML5` techs element.
20125
+ *
20126
+ * @return {Tech~SourceObject|undefined}
20127
+ * - The current source object when a source is not passed in.
20128
+ * - undefined when setting
20129
+ *
20130
+ * @deprecated Since version 5.
20131
+ */
20132
+
20133
+
20134
+ Html5.prototype.src = function src(_src) {
20135
+ if (_src === undefined) {
20136
+ return this.el_.src;
20137
+ }
20138
+
20139
+ // Setting src through `src` instead of `setSrc` will be deprecated
20140
+ this.setSrc(_src);
20141
+ };
20142
+
20143
+ /**
20144
+ * Reset the tech by removing all sources and then calling
20145
+ * {@link Html5.resetMediaElement}.
20146
+ */
20147
+
20148
+
20149
+ Html5.prototype.reset = function reset() {
20150
+ Html5.resetMediaElement(this.el_);
20151
+ };
20152
+
20153
+ /**
20154
+ * Get the current source on the `HTML5` Tech. Falls back to returning the source from
20155
+ * the HTML5 media element.
20156
+ *
20157
+ * @return {Tech~SourceObject}
20158
+ * The current source object from the HTML5 tech. With a fallback to the
20159
+ * elements source.
20160
+ */
20161
+
20162
+
20163
+ Html5.prototype.currentSrc = function currentSrc() {
20164
+ if (this.currentSource_) {
20165
+ return this.currentSource_.src;
20166
+ }
20167
+ return this.el_.currentSrc;
20168
+ };
20169
+
20170
+ /**
20171
+ * Set controls attribute for the HTML5 media Element.
20172
+ *
20173
+ * @param {string} val
20174
+ * Value to set the controls attribute to
20175
+ */
20176
+
20177
+
20178
+ Html5.prototype.setControls = function setControls(val) {
20179
+ this.el_.controls = !!val;
20180
+ };
20181
+
20182
+ /**
20183
+ * Create and returns a remote {@link TextTrack} object.
20184
+ *
20185
+ * @param {string} kind
20186
+ * `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata)
20187
+ *
20188
+ * @param {string} [label]
20189
+ * Label to identify the text track
20190
+ *
20191
+ * @param {string} [language]
20192
+ * Two letter language abbreviation
20193
+ *
20194
+ * @return {TextTrack}
20195
+ * The TextTrack that gets created.
20196
+ */
20197
+
20198
+
20199
+ Html5.prototype.addTextTrack = function addTextTrack(kind, label, language) {
20200
+ if (!this.featuresNativeTextTracks) {
20201
+ return _Tech.prototype.addTextTrack.call(this, kind, label, language);
20202
+ }
20203
+
20204
+ return this.el_.addTextTrack(kind, label, language);
20205
+ };
20206
+
20207
+ /**
20208
+ * Creates either native TextTrack or an emulated TextTrack depending
20209
+ * on the value of `featuresNativeTextTracks`
20210
+ *
20211
+ * @param {Object} options
20212
+ * The object should contain the options to initialize the TextTrack with.
20213
+ *
20214
+ * @param {string} [options.kind]
20215
+ * `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata).
20216
+ *
20217
+ * @param {string} [options.label]
20218
+ * Label to identify the text track
20219
+ *
20220
+ * @param {string} [options.language]
20221
+ * Two letter language abbreviation.
20222
+ *
20223
+ * @param {boolean} [options.default]
20224
+ * Default this track to on.
20225
+ *
20226
+ * @param {string} [options.id]
20227
+ * The internal id to assign this track.
20228
+ *
20229
+ * @param {string} [options.src]
20230
+ * A source url for the track.
20231
+ *
20232
+ * @return {HTMLTrackElement}
20233
+ * The track element that gets created.
20234
+ */
20235
+
20236
+
20237
+ Html5.prototype.createRemoteTextTrack = function createRemoteTextTrack(options) {
20238
+ if (!this.featuresNativeTextTracks) {
20239
+ return _Tech.prototype.createRemoteTextTrack.call(this, options);
20240
+ }
20241
+ var htmlTrackElement = document_1.createElement('track');
20242
+
20243
+ if (options.kind) {
20244
+ htmlTrackElement.kind = options.kind;
20245
+ }
20246
+ if (options.label) {
20247
+ htmlTrackElement.label = options.label;
20248
+ }
20249
+ if (options.language || options.srclang) {
20250
+ htmlTrackElement.srclang = options.language || options.srclang;
20251
+ }
20252
+ if (options.default) {
20253
+ htmlTrackElement.default = options.default;
20254
+ }
20255
+ if (options.id) {
20256
+ htmlTrackElement.id = options.id;
20257
+ }
20258
+ if (options.src) {
20259
+ htmlTrackElement.src = options.src;
20260
+ }
20261
+
20262
+ return htmlTrackElement;
20263
+ };
20264
+
20265
+ /**
20266
+ * Creates a remote text track object and returns an html track element.
20267
+ *
20268
+ * @param {Object} options The object should contain values for
20269
+ * kind, language, label, and src (location of the WebVTT file)
20270
+ * @param {Boolean} [manualCleanup=true] if set to false, the TextTrack will be
20271
+ * automatically removed from the video element whenever the source changes
20272
+ * @return {HTMLTrackElement} An Html Track Element.
20273
+ * This can be an emulated {@link HTMLTrackElement} or a native one.
20274
+ * @deprecated The default value of the "manualCleanup" parameter will default
20275
+ * to "false" in upcoming versions of Video.js
20276
+ */
20277
+
20278
+
20279
+ Html5.prototype.addRemoteTextTrack = function addRemoteTextTrack(options, manualCleanup) {
20280
+ var htmlTrackElement = _Tech.prototype.addRemoteTextTrack.call(this, options, manualCleanup);
20281
+
20282
+ if (this.featuresNativeTextTracks) {
20283
+ this.el().appendChild(htmlTrackElement);
20284
+ }
20285
+
20286
+ return htmlTrackElement;
20287
+ };
20288
+
20289
+ /**
20290
+ * Remove remote `TextTrack` from `TextTrackList` object
20291
+ *
20292
+ * @param {TextTrack} track
20293
+ * `TextTrack` object to remove
20294
+ */
20295
+
20296
+
20297
+ Html5.prototype.removeRemoteTextTrack = function removeRemoteTextTrack(track) {
20298
+ _Tech.prototype.removeRemoteTextTrack.call(this, track);
20299
+
20300
+ if (this.featuresNativeTextTracks) {
20301
+ var tracks = this.$$('track');
20302
+
20303
+ var i = tracks.length;
20304
+
20305
+ while (i--) {
20306
+ if (track === tracks[i] || track === tracks[i].track) {
20307
+ this.el().removeChild(tracks[i]);
20308
+ }
20309
+ }
20310
+ }
20311
+ };
20312
+
20313
+ /**
20314
+ * Gets available media playback quality metrics as specified by the W3C's Media
20315
+ * Playback Quality API.
20316
+ *
20317
+ * @see [Spec]{@link https://wicg.github.io/media-playback-quality}
20318
+ *
20319
+ * @return {Object}
20320
+ * An object with supported media playback quality metrics
20321
+ */
20322
+
20323
+
20324
+ Html5.prototype.getVideoPlaybackQuality = function getVideoPlaybackQuality() {
20325
+ if (typeof this.el().getVideoPlaybackQuality === 'function') {
20326
+ return this.el().getVideoPlaybackQuality();
20327
+ }
20328
+
20329
+ var videoPlaybackQuality = {};
20330
+
20331
+ if (typeof this.el().webkitDroppedFrameCount !== 'undefined' && typeof this.el().webkitDecodedFrameCount !== 'undefined') {
20332
+ videoPlaybackQuality.droppedVideoFrames = this.el().webkitDroppedFrameCount;
20333
+ videoPlaybackQuality.totalVideoFrames = this.el().webkitDecodedFrameCount;
20334
+ }
20335
+
20336
+ if (window_1.performance && typeof window_1.performance.now === 'function') {
20337
+ videoPlaybackQuality.creationTime = window_1.performance.now();
20338
+ } else if (window_1.performance && window_1.performance.timing && typeof window_1.performance.timing.navigationStart === 'number') {
20339
+ videoPlaybackQuality.creationTime = window_1.Date.now() - window_1.performance.timing.navigationStart;
20340
+ }
20341
+
20342
+ return videoPlaybackQuality;
20343
+ };
20344
+
20345
+ return Html5;
20346
+ }(Tech);
20347
+
20348
+ /* HTML5 Support Testing ---------------------------------------------------- */
20349
+
20350
+ if (isReal()) {
20351
+
20352
+ /**
20353
+ * Element for testing browser HTML5 media capabilities
20354
+ *
20355
+ * @type {Element}
20356
+ * @constant
20357
+ * @private
20358
+ */
20359
+ Html5.TEST_VID = document_1.createElement('video');
20360
+ var track = document_1.createElement('track');
20361
+
20362
+ track.kind = 'captions';
20363
+ track.srclang = 'en';
20364
+ track.label = 'English';
20365
+ Html5.TEST_VID.appendChild(track);
20366
+ }
20367
+
20368
+ /**
20369
+ * Check if HTML5 media is supported by this browser/device.
20370
+ *
20371
+ * @return {boolean}
20372
+ * - True if HTML5 media is supported.
20373
+ * - False if HTML5 media is not supported.
20374
+ */
20375
+ Html5.isSupported = function () {
20376
+ // IE with no Media Player is a LIAR! (#984)
20377
+ try {
20378
+ Html5.TEST_VID.volume = 0.5;
20379
+ } catch (e) {
20380
+ return false;
20381
+ }
20382
+
20383
+ return !!(Html5.TEST_VID && Html5.TEST_VID.canPlayType);
20384
+ };
20385
+
20386
+ /**
20387
+ * Check if the tech can support the given type
20388
+ *
20389
+ * @param {string} type
20390
+ * The mimetype to check
20391
+ * @return {string} 'probably', 'maybe', or '' (empty string)
20392
+ */
20393
+ Html5.canPlayType = function (type) {
20394
+ return Html5.TEST_VID.canPlayType(type);
20395
+ };
20396
+
20397
+ /**
20398
+ * Check if the tech can support the given source
20399
+ * @param {Object} srcObj
20400
+ * The source object
20401
+ * @param {Object} options
20402
+ * The options passed to the tech
20403
+ * @return {string} 'probably', 'maybe', or '' (empty string)
20404
+ */
20405
+ Html5.canPlaySource = function (srcObj, options) {
20406
+ return Html5.canPlayType(srcObj.type);
20407
+ };
20408
+
20409
+ /**
20410
+ * Check if the volume can be changed in this browser/device.
20411
+ * Volume cannot be changed in a lot of mobile devices.
20412
+ * Specifically, it can't be changed from 1 on iOS.
20413
+ *
20414
+ * @return {boolean}
20415
+ * - True if volume can be controlled
20416
+ * - False otherwise
20417
+ */
20418
+ Html5.canControlVolume = function () {
20419
+ // IE will error if Windows Media Player not installed #3315
20420
+ try {
20421
+ var volume = Html5.TEST_VID.volume;
20422
+
20423
+ Html5.TEST_VID.volume = volume / 2 + 0.1;
20424
+ return volume !== Html5.TEST_VID.volume;
20425
+ } catch (e) {
20426
+ return false;
20427
+ }
20428
+ };
20429
+
20430
+ /**
20431
+ * Check if the volume can be muted in this browser/device.
20432
+ * Some devices, e.g. iOS, don't allow changing volume
20433
+ * but permits muting/unmuting.
20434
+ *
20435
+ * @return {bolean}
20436
+ * - True if volume can be muted
20437
+ * - False otherwise
20438
+ */
20439
+ Html5.canMuteVolume = function () {
20440
+ try {
20441
+ var muted = Html5.TEST_VID.muted;
20442
+
20443
+ // in some versions of iOS muted property doesn't always
20444
+ // work, so we want to set both property and attribute
20445
+ Html5.TEST_VID.muted = !muted;
20446
+ if (Html5.TEST_VID.muted) {
20447
+ setAttribute(Html5.TEST_VID, 'muted', 'muted');
20448
+ } else {
20449
+ removeAttribute(Html5.TEST_VID, 'muted', 'muted');
20450
+ }
20451
+ return muted !== Html5.TEST_VID.muted;
20452
+ } catch (e) {
20453
+ return false;
20454
+ }
20455
+ };
20456
+
20457
+ /**
20458
+ * Check if the playback rate can be changed in this browser/device.
20459
+ *
20460
+ * @return {boolean}
20461
+ * - True if playback rate can be controlled
20462
+ * - False otherwise
20463
+ */
20464
+ Html5.canControlPlaybackRate = function () {
20465
+ // Playback rate API is implemented in Android Chrome, but doesn't do anything
20466
+ // https://github.com/videojs/video.js/issues/3180
20467
+ if (IS_ANDROID && IS_CHROME && CHROME_VERSION < 58) {
20468
+ return false;
20469
+ }
20470
+ // IE will error if Windows Media Player not installed #3315
20471
+ try {
20472
+ var playbackRate = Html5.TEST_VID.playbackRate;
20473
+
20474
+ Html5.TEST_VID.playbackRate = playbackRate / 2 + 0.1;
20475
+ return playbackRate !== Html5.TEST_VID.playbackRate;
20476
+ } catch (e) {
20477
+ return false;
20478
+ }
20479
+ };
20480
+
20481
+ /**
20482
+ * Check if we can override a video/audio elements attributes, with
20483
+ * Object.defineProperty.
20484
+ *
20485
+ * @return {boolean}
20486
+ * - True if builtin attributes can be overridden
20487
+ * - False otherwise
20488
+ */
20489
+ Html5.canOverrideAttributes = function () {
20490
+ // if we cannot overwrite the src/innerHTML property, there is no support
20491
+ // iOS 7 safari for instance cannot do this.
20492
+ try {
20493
+ var noop = function noop() {};
20494
+
20495
+ Object.defineProperty(document_1.createElement('video'), 'src', { get: noop, set: noop });
20496
+ Object.defineProperty(document_1.createElement('audio'), 'src', { get: noop, set: noop });
20497
+ Object.defineProperty(document_1.createElement('video'), 'innerHTML', { get: noop, set: noop });
20498
+ Object.defineProperty(document_1.createElement('audio'), 'innerHTML', { get: noop, set: noop });
20499
+ } catch (e) {
20500
+ return false;
20501
+ }
20502
+
20503
+ return true;
20504
+ };
20505
+
20506
+ /**
20507
+ * Check to see if native `TextTrack`s are supported by this browser/device.
20508
+ *
20509
+ * @return {boolean}
20510
+ * - True if native `TextTrack`s are supported.
20511
+ * - False otherwise
20512
+ */
20513
+ Html5.supportsNativeTextTracks = function () {
20514
+ return IS_ANY_SAFARI || IS_IOS && IS_CHROME;
20515
+ };
20516
+
20517
+ /**
20518
+ * Check to see if native `VideoTrack`s are supported by this browser/device
20519
+ *
20520
+ * @return {boolean}
20521
+ * - True if native `VideoTrack`s are supported.
20522
+ * - False otherwise
20523
+ */
20524
+ Html5.supportsNativeVideoTracks = function () {
20525
+ return !!(Html5.TEST_VID && Html5.TEST_VID.videoTracks);
20526
+ };
20527
+
20528
+ /**
20529
+ * Check to see if native `AudioTrack`s are supported by this browser/device
20530
+ *
20531
+ * @return {boolean}
20532
+ * - True if native `AudioTrack`s are supported.
20533
+ * - False otherwise
20534
+ */
20535
+ Html5.supportsNativeAudioTracks = function () {
20536
+ return !!(Html5.TEST_VID && Html5.TEST_VID.audioTracks);
20537
+ };
20538
+
20539
+ /**
20540
+ * An array of events available on the Html5 tech.
20541
+ *
20542
+ * @private
20543
+ * @type {Array}
20544
+ */
20545
+ Html5.Events = ['loadstart', 'suspend', 'abort', 'error', 'emptied', 'stalled', 'loadedmetadata', 'loadeddata', 'canplay', 'canplaythrough', 'playing', 'waiting', 'seeking', 'seeked', 'ended', 'durationchange', 'timeupdate', 'progress', 'play', 'pause', 'ratechange', 'resize', 'volumechange'];
20546
+
20547
+ /**
20548
+ * Boolean indicating whether the `Tech` supports volume control.
20549
+ *
20550
+ * @type {boolean}
20551
+ * @default {@link Html5.canControlVolume}
20552
+ */
20553
+ Html5.prototype.featuresVolumeControl = Html5.canControlVolume();
20554
+
20555
+ /**
20556
+ * Boolean indicating whether the `Tech` supports muting volume.
20557
+ *
20558
+ * @type {bolean}
20559
+ * @default {@link Html5.canMuteVolume}
20560
+ */
20561
+ Html5.prototype.featuresMuteControl = Html5.canMuteVolume();
20562
+
20563
+ /**
20564
+ * Boolean indicating whether the `Tech` supports changing the speed at which the media
20565
+ * plays. Examples:
20566
+ * - Set player to play 2x (twice) as fast
20567
+ * - Set player to play 0.5x (half) as fast
20568
+ *
20569
+ * @type {boolean}
20570
+ * @default {@link Html5.canControlPlaybackRate}
20571
+ */
20572
+ Html5.prototype.featuresPlaybackRate = Html5.canControlPlaybackRate();
20573
+
20574
+ /**
20575
+ * Boolean indicating whether the `Tech` supports the `sourceset` event.
20576
+ *
20577
+ * @type {boolean}
20578
+ * @default
20579
+ */
20580
+ Html5.prototype.featuresSourceset = Html5.canOverrideAttributes();
20581
+
20582
+ /**
20583
+ * Boolean indicating whether the `HTML5` tech currently supports the media element
20584
+ * moving in the DOM. iOS breaks if you move the media element, so this is set this to
20585
+ * false there. Everywhere else this should be true.
20586
+ *
20587
+ * @type {boolean}
20588
+ * @default
20589
+ */
20590
+ Html5.prototype.movingMediaElementInDOM = !IS_IOS;
20591
+
20592
+ // TODO: Previous comment: No longer appears to be used. Can probably be removed.
20593
+ // Is this true?
20594
+ /**
20595
+ * Boolean indicating whether the `HTML5` tech currently supports automatic media resize
20596
+ * when going into fullscreen.
20597
+ *
20598
+ * @type {boolean}
20599
+ * @default
20600
+ */
20601
+ Html5.prototype.featuresFullscreenResize = true;
20602
+
20603
+ /**
20604
+ * Boolean indicating whether the `HTML5` tech currently supports the progress event.
20605
+ * If this is false, manual `progress` events will be triggered instead.
20606
+ *
20607
+ * @type {boolean}
20608
+ * @default
20609
+ */
20610
+ Html5.prototype.featuresProgressEvents = true;
20611
+
20612
+ /**
20613
+ * Boolean indicating whether the `HTML5` tech currently supports the timeupdate event.
20614
+ * If this is false, manual `timeupdate` events will be triggered instead.
20615
+ *
20616
+ * @default
20617
+ */
20618
+ Html5.prototype.featuresTimeupdateEvents = true;
20619
+
20620
+ /**
20621
+ * Boolean indicating whether the `HTML5` tech currently supports native `TextTrack`s.
20622
+ *
20623
+ * @type {boolean}
20624
+ * @default {@link Html5.supportsNativeTextTracks}
20625
+ */
20626
+ Html5.prototype.featuresNativeTextTracks = Html5.supportsNativeTextTracks();
20627
+
20628
+ /**
20629
+ * Boolean indicating whether the `HTML5` tech currently supports native `VideoTrack`s.
20630
+ *
20631
+ * @type {boolean}
20632
+ * @default {@link Html5.supportsNativeVideoTracks}
20633
+ */
20634
+ Html5.prototype.featuresNativeVideoTracks = Html5.supportsNativeVideoTracks();
20635
+
20636
+ /**
20637
+ * Boolean indicating whether the `HTML5` tech currently supports native `AudioTrack`s.
20638
+ *
20639
+ * @type {boolean}
20640
+ * @default {@link Html5.supportsNativeAudioTracks}
20641
+ */
20642
+ Html5.prototype.featuresNativeAudioTracks = Html5.supportsNativeAudioTracks();
20643
+
20644
+ // HTML5 Feature detection and Device Fixes --------------------------------- //
20645
+ var canPlayType = Html5.TEST_VID && Html5.TEST_VID.constructor.prototype.canPlayType;
20646
+ var mpegurlRE = /^application\/(?:x-|vnd\.apple\.)mpegurl/i;
20647
+
20648
+ Html5.patchCanPlayType = function () {
20649
+
20650
+ // Android 4.0 and above can play HLS to some extent but it reports being unable to do so
20651
+ // Firefox and Chrome report correctly
20652
+ if (ANDROID_VERSION >= 4.0 && !IS_FIREFOX && !IS_CHROME) {
20653
+ Html5.TEST_VID.constructor.prototype.canPlayType = function (type) {
20654
+ if (type && mpegurlRE.test(type)) {
20655
+ return 'maybe';
20656
+ }
20657
+ return canPlayType.call(this, type);
20658
+ };
20659
+ }
20660
+ };
20661
+
20662
+ Html5.unpatchCanPlayType = function () {
20663
+ var r = Html5.TEST_VID.constructor.prototype.canPlayType;
20664
+
20665
+ Html5.TEST_VID.constructor.prototype.canPlayType = canPlayType;
20666
+ return r;
20667
+ };
20668
+
20669
+ // by default, patch the media element
20670
+ Html5.patchCanPlayType();
20671
+
20672
+ Html5.disposeMediaElement = function (el) {
20673
+ if (!el) {
20674
+ return;
20675
+ }
20676
+
20677
+ if (el.parentNode) {
20678
+ el.parentNode.removeChild(el);
20679
+ }
20680
+
20681
+ // remove any child track or source nodes to prevent their loading
20682
+ while (el.hasChildNodes()) {
20683
+ el.removeChild(el.firstChild);
20684
+ }
20685
+
20686
+ // remove any src reference. not setting `src=''` because that causes a warning
20687
+ // in firefox
20688
+ el.removeAttribute('src');
20689
+
20690
+ // force the media element to update its loading state by calling load()
20691
+ // however IE on Windows 7N has a bug that throws an error so need a try/catch (#793)
20692
+ if (typeof el.load === 'function') {
20693
+ // wrapping in an iife so it's not deoptimized (#1060#discussion_r10324473)
20694
+ (function () {
20695
+ try {
20696
+ el.load();
20697
+ } catch (e) {
20698
+ // not supported
20699
+ }
20700
+ })();
20701
+ }
20702
+ };
20703
+
20704
+ Html5.resetMediaElement = function (el) {
20705
+ if (!el) {
20706
+ return;
20707
+ }
20708
+
20709
+ var sources = el.querySelectorAll('source');
20710
+ var i = sources.length;
20711
+
20712
+ while (i--) {
20713
+ el.removeChild(sources[i]);
20714
+ }
20715
+
20716
+ // remove any src reference.
20717
+ // not setting `src=''` because that throws an error
20718
+ el.removeAttribute('src');
20719
+
20720
+ if (typeof el.load === 'function') {
20721
+ // wrapping in an iife so it's not deoptimized (#1060#discussion_r10324473)
20722
+ (function () {
20723
+ try {
20724
+ el.load();
20725
+ } catch (e) {
20726
+ // satisfy linter
20727
+ }
20728
+ })();
20729
+ }
20730
+ };
20731
+
20732
+ /* Native HTML5 element property wrapping ----------------------------------- */
20733
+ // Wrap native boolean attributes with getters that check both property and attribute
20734
+ // The list is as followed:
20735
+ // muted, defaultMuted, autoplay, controls, loop, playsinline
20736
+ [
20737
+ /**
20738
+ * Get the value of `muted` from the media element. `muted` indicates
20739
+ * that the volume for the media should be set to silent. This does not actually change
20740
+ * the `volume` attribute.
20741
+ *
20742
+ * @method Html5#muted
20743
+ * @return {boolean}
20744
+ * - True if the value of `volume` should be ignored and the audio set to silent.
20745
+ * - False if the value of `volume` should be used.
20746
+ *
20747
+ * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-muted}
20748
+ */
20749
+ 'muted',
20750
+
20751
+ /**
20752
+ * Get the value of `defaultMuted` from the media element. `defaultMuted` indicates
20753
+ * whether the media should start muted or not. Only changes the default state of the
20754
+ * media. `muted` and `defaultMuted` can have different values. {@link Html5#muted} indicates the
20755
+ * current state.
20756
+ *
20757
+ * @method Html5#defaultMuted
20758
+ * @return {boolean}
20759
+ * - The value of `defaultMuted` from the media element.
20760
+ * - True indicates that the media should start muted.
20761
+ * - False indicates that the media should not start muted
20762
+ *
20763
+ * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-defaultmuted}
20764
+ */
20765
+ 'defaultMuted',
20766
+
20767
+ /**
20768
+ * Get the value of `autoplay` from the media element. `autoplay` indicates
20769
+ * that the media should start to play as soon as the page is ready.
20770
+ *
20771
+ * @method Html5#autoplay
20772
+ * @return {boolean}
20773
+ * - The value of `autoplay` from the media element.
20774
+ * - True indicates that the media should start as soon as the page loads.
20775
+ * - False indicates that the media should not start as soon as the page loads.
20776
+ *
20777
+ * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-autoplay}
20778
+ */
20779
+ 'autoplay',
20780
+
20781
+ /**
20782
+ * Get the value of `controls` from the media element. `controls` indicates
20783
+ * whether the native media controls should be shown or hidden.
20784
+ *
20785
+ * @method Html5#controls
20786
+ * @return {boolean}
20787
+ * - The value of `controls` from the media element.
20788
+ * - True indicates that native controls should be showing.
20789
+ * - False indicates that native controls should be hidden.
20790
+ *
20791
+ * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-controls}
20792
+ */
20793
+ 'controls',
20794
+
20795
+ /**
20796
+ * Get the value of `loop` from the media element. `loop` indicates
20797
+ * that the media should return to the start of the media and continue playing once
20798
+ * it reaches the end.
20799
+ *
20800
+ * @method Html5#loop
20801
+ * @return {boolean}
20802
+ * - The value of `loop` from the media element.
20803
+ * - True indicates that playback should seek back to start once
20804
+ * the end of a media is reached.
20805
+ * - False indicates that playback should not loop back to the start when the
20806
+ * end of the media is reached.
20807
+ *
20808
+ * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-loop}
20809
+ */
20810
+ 'loop',
20811
+
20812
+ /**
20813
+ * Get the value of `playsinline` from the media element. `playsinline` indicates
20814
+ * to the browser that non-fullscreen playback is preferred when fullscreen
20815
+ * playback is the native default, such as in iOS Safari.
20816
+ *
20817
+ * @method Html5#playsinline
20818
+ * @return {boolean}
20819
+ * - The value of `playsinline` from the media element.
20820
+ * - True indicates that the media should play inline.
20821
+ * - False indicates that the media should not play inline.
20822
+ *
20823
+ * @see [Spec]{@link https://html.spec.whatwg.org/#attr-video-playsinline}
20824
+ */
20825
+ 'playsinline'].forEach(function (prop) {
20826
+ Html5.prototype[prop] = function () {
20827
+ return this.el_[prop] || this.el_.hasAttribute(prop);
20828
+ };
20829
+ });
20830
+
20831
+ // Wrap native boolean attributes with setters that set both property and attribute
20832
+ // The list is as followed:
20833
+ // setMuted, setDefaultMuted, setAutoplay, setLoop, setPlaysinline
20834
+ // setControls is special-cased above
20835
+ [
20836
+ /**
20837
+ * Set the value of `muted` on the media element. `muted` indicates that the current
20838
+ * audio level should be silent.
20839
+ *
20840
+ * @method Html5#setMuted
20841
+ * @param {boolean} muted
20842
+ * - True if the audio should be set to silent
20843
+ * - False otherwise
20844
+ *
20845
+ * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-muted}
20846
+ */
20847
+ 'muted',
20848
+
20849
+ /**
20850
+ * Set the value of `defaultMuted` on the media element. `defaultMuted` indicates that the current
20851
+ * audio level should be silent, but will only effect the muted level on intial playback..
20852
+ *
20853
+ * @method Html5.prototype.setDefaultMuted
20854
+ * @param {boolean} defaultMuted
20855
+ * - True if the audio should be set to silent
20856
+ * - False otherwise
20857
+ *
20858
+ * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-defaultmuted}
20859
+ */
20860
+ 'defaultMuted',
20861
+
20862
+ /**
20863
+ * Set the value of `autoplay` on the media element. `autoplay` indicates
20864
+ * that the media should start to play as soon as the page is ready.
20865
+ *
20866
+ * @method Html5#setAutoplay
20867
+ * @param {boolean} autoplay
20868
+ * - True indicates that the media should start as soon as the page loads.
20869
+ * - False indicates that the media should not start as soon as the page loads.
20870
+ *
20871
+ * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-autoplay}
20872
+ */
20873
+ 'autoplay',
20874
+
20875
+ /**
20876
+ * Set the value of `loop` on the media element. `loop` indicates
20877
+ * that the media should return to the start of the media and continue playing once
20878
+ * it reaches the end.
20879
+ *
20880
+ * @method Html5#setLoop
20881
+ * @param {boolean} loop
20882
+ * - True indicates that playback should seek back to start once
20883
+ * the end of a media is reached.
20884
+ * - False indicates that playback should not loop back to the start when the
20885
+ * end of the media is reached.
20886
+ *
20887
+ * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-loop}
20888
+ */
20889
+ 'loop',
20890
+
20891
+ /**
20892
+ * Set the value of `playsinline` from the media element. `playsinline` indicates
20893
+ * to the browser that non-fullscreen playback is preferred when fullscreen
20894
+ * playback is the native default, such as in iOS Safari.
20895
+ *
20896
+ * @method Html5#setPlaysinline
20897
+ * @param {boolean} playsinline
20898
+ * - True indicates that the media should play inline.
20899
+ * - False indicates that the media should not play inline.
20900
+ *
20901
+ * @see [Spec]{@link https://html.spec.whatwg.org/#attr-video-playsinline}
20902
+ */
20903
+ 'playsinline'].forEach(function (prop) {
20904
+ Html5.prototype['set' + toTitleCase(prop)] = function (v) {
20905
+ this.el_[prop] = v;
20906
+
20907
+ if (v) {
20908
+ this.el_.setAttribute(prop, prop);
20909
+ } else {
20910
+ this.el_.removeAttribute(prop);
20911
+ }
20912
+ };
20913
+ });
20914
+
20915
+ // Wrap native properties with a getter
20916
+ // The list is as followed
20917
+ // paused, currentTime, buffered, volume, poster, preload, error, seeking
20918
+ // seekable, ended, playbackRate, defaultPlaybackRate, played, networkState
20919
+ // readyState, videoWidth, videoHeight
20920
+ [
20921
+ /**
20922
+ * Get the value of `paused` from the media element. `paused` indicates whether the media element
20923
+ * is currently paused or not.
20924
+ *
20925
+ * @method Html5#paused
20926
+ * @return {boolean}
20927
+ * The value of `paused` from the media element.
20928
+ *
20929
+ * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-paused}
20930
+ */
20931
+ 'paused',
20932
+
20933
+ /**
20934
+ * Get the value of `currentTime` from the media element. `currentTime` indicates
20935
+ * the current second that the media is at in playback.
20936
+ *
20937
+ * @method Html5#currentTime
20938
+ * @return {number}
20939
+ * The value of `currentTime` from the media element.
20940
+ *
20941
+ * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-currenttime}
20942
+ */
20943
+ 'currentTime',
20944
+
20945
+ /**
20946
+ * Get the value of `buffered` from the media element. `buffered` is a `TimeRange`
20947
+ * object that represents the parts of the media that are already downloaded and
20948
+ * available for playback.
20949
+ *
20950
+ * @method Html5#buffered
20951
+ * @return {TimeRange}
20952
+ * The value of `buffered` from the media element.
20953
+ *
20954
+ * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-buffered}
20955
+ */
20956
+ 'buffered',
20957
+
20958
+ /**
20959
+ * Get the value of `volume` from the media element. `volume` indicates
20960
+ * the current playback volume of audio for a media. `volume` will be a value from 0
20961
+ * (silent) to 1 (loudest and default).
20962
+ *
20963
+ * @method Html5#volume
20964
+ * @return {number}
20965
+ * The value of `volume` from the media element. Value will be between 0-1.
20966
+ *
20967
+ * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-a-volume}
20968
+ */
20969
+ 'volume',
20970
+
20971
+ /**
20972
+ * Get the value of `poster` from the media element. `poster` indicates
20973
+ * that the url of an image file that can/will be shown when no media data is available.
20974
+ *
20975
+ * @method Html5#poster
20976
+ * @return {string}
20977
+ * The value of `poster` from the media element. Value will be a url to an
20978
+ * image.
20979
+ *
20980
+ * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-video-poster}
20981
+ */
20982
+ 'poster',
20983
+
20984
+ /**
20985
+ * Get the value of `preload` from the media element. `preload` indicates
20986
+ * what should download before the media is interacted with. It can have the following
20987
+ * values:
20988
+ * - none: nothing should be downloaded
20989
+ * - metadata: poster and the first few frames of the media may be downloaded to get
20990
+ * media dimensions and other metadata
20991
+ * - auto: allow the media and metadata for the media to be downloaded before
20992
+ * interaction
20993
+ *
20994
+ * @method Html5#preload
20995
+ * @return {string}
20996
+ * The value of `preload` from the media element. Will be 'none', 'metadata',
20997
+ * or 'auto'.
20998
+ *
20999
+ * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-preload}
21000
+ */
21001
+ 'preload',
21002
+
21003
+ /**
21004
+ * Get the value of the `error` from the media element. `error` indicates any
21005
+ * MediaError that may have occurred during playback. If error returns null there is no
21006
+ * current error.
21007
+ *
21008
+ * @method Html5#error
21009
+ * @return {MediaError|null}
21010
+ * The value of `error` from the media element. Will be `MediaError` if there
21011
+ * is a current error and null otherwise.
21012
+ *
21013
+ * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-error}
21014
+ */
21015
+ 'error',
21016
+
21017
+ /**
21018
+ * Get the value of `seeking` from the media element. `seeking` indicates whether the
21019
+ * media is currently seeking to a new position or not.
21020
+ *
21021
+ * @method Html5#seeking
21022
+ * @return {boolean}
21023
+ * - The value of `seeking` from the media element.
21024
+ * - True indicates that the media is currently seeking to a new position.
21025
+ * - False indicates that the media is not seeking to a new position at this time.
21026
+ *
21027
+ * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-seeking}
21028
+ */
21029
+ 'seeking',
21030
+
21031
+ /**
21032
+ * Get the value of `seekable` from the media element. `seekable` returns a
21033
+ * `TimeRange` object indicating ranges of time that can currently be `seeked` to.
21034
+ *
21035
+ * @method Html5#seekable
21036
+ * @return {TimeRange}
21037
+ * The value of `seekable` from the media element. A `TimeRange` object
21038
+ * indicating the current ranges of time that can be seeked to.
21039
+ *
21040
+ * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-seekable}
21041
+ */
21042
+ 'seekable',
21043
+
21044
+ /**
21045
+ * Get the value of `ended` from the media element. `ended` indicates whether
21046
+ * the media has reached the end or not.
21047
+ *
21048
+ * @method Html5#ended
21049
+ * @return {boolean}
21050
+ * - The value of `ended` from the media element.
21051
+ * - True indicates that the media has ended.
21052
+ * - False indicates that the media has not ended.
21053
+ *
21054
+ * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-ended}
21055
+ */
21056
+ 'ended',
21057
+
21058
+ /**
21059
+ * Get the value of `playbackRate` from the media element. `playbackRate` indicates
21060
+ * the rate at which the media is currently playing back. Examples:
21061
+ * - if playbackRate is set to 2, media will play twice as fast.
21062
+ * - if playbackRate is set to 0.5, media will play half as fast.
21063
+ *
21064
+ * @method Html5#playbackRate
21065
+ * @return {number}
21066
+ * The value of `playbackRate` from the media element. A number indicating
21067
+ * the current playback speed of the media, where 1 is normal speed.
21068
+ *
21069
+ * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-playbackrate}
21070
+ */
21071
+ 'playbackRate',
21072
+
21073
+ /**
21074
+ * Get the value of `defaultPlaybackRate` from the media element. `defaultPlaybackRate` indicates
21075
+ * the rate at which the media is currently playing back. This value will not indicate the current
21076
+ * `playbackRate` after playback has started, use {@link Html5#playbackRate} for that.
21077
+ *
21078
+ * Examples:
21079
+ * - if defaultPlaybackRate is set to 2, media will play twice as fast.
21080
+ * - if defaultPlaybackRate is set to 0.5, media will play half as fast.
21081
+ *
21082
+ * @method Html5.prototype.defaultPlaybackRate
21083
+ * @return {number}
21084
+ * The value of `defaultPlaybackRate` from the media element. A number indicating
21085
+ * the current playback speed of the media, where 1 is normal speed.
21086
+ *
21087
+ * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-playbackrate}
21088
+ */
21089
+ 'defaultPlaybackRate',
21090
+
21091
+ /**
21092
+ * Get the value of `played` from the media element. `played` returns a `TimeRange`
21093
+ * object representing points in the media timeline that have been played.
21094
+ *
21095
+ * @method Html5#played
21096
+ * @return {TimeRange}
21097
+ * The value of `played` from the media element. A `TimeRange` object indicating
21098
+ * the ranges of time that have been played.
21099
+ *
21100
+ * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-played}
21101
+ */
21102
+ 'played',
21103
+
21104
+ /**
21105
+ * Get the value of `networkState` from the media element. `networkState` indicates
21106
+ * the current network state. It returns an enumeration from the following list:
21107
+ * - 0: NETWORK_EMPTY
21108
+ * - 1: NETWORK_IDLE
21109
+ * - 2: NETWORK_LOADING
21110
+ * - 3: NETWORK_NO_SOURCE
21111
+ *
21112
+ * @method Html5#networkState
21113
+ * @return {number}
21114
+ * The value of `networkState` from the media element. This will be a number
21115
+ * from the list in the description.
21116
+ *
21117
+ * @see [Spec] {@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-networkstate}
21118
+ */
21119
+ 'networkState',
21120
+
21121
+ /**
21122
+ * Get the value of `readyState` from the media element. `readyState` indicates
21123
+ * the current state of the media element. It returns an enumeration from the
21124
+ * following list:
21125
+ * - 0: HAVE_NOTHING
21126
+ * - 1: HAVE_METADATA
21127
+ * - 2: HAVE_CURRENT_DATA
21128
+ * - 3: HAVE_FUTURE_DATA
21129
+ * - 4: HAVE_ENOUGH_DATA
21130
+ *
21131
+ * @method Html5#readyState
21132
+ * @return {number}
21133
+ * The value of `readyState` from the media element. This will be a number
21134
+ * from the list in the description.
21135
+ *
21136
+ * @see [Spec] {@link https://www.w3.org/TR/html5/embedded-content-0.html#ready-states}
21137
+ */
21138
+ 'readyState',
21139
+
21140
+ /**
21141
+ * Get the value of `videoWidth` from the video element. `videoWidth` indicates
21142
+ * the current width of the video in css pixels.
21143
+ *
21144
+ * @method Html5#videoWidth
21145
+ * @return {number}
21146
+ * The value of `videoWidth` from the video element. This will be a number
21147
+ * in css pixels.
21148
+ *
21149
+ * @see [Spec] {@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-video-videowidth}
21150
+ */
21151
+ 'videoWidth',
21152
+
21153
+ /**
21154
+ * Get the value of `videoHeight` from the video element. `videoHeight` indicates
21155
+ * the current height of the video in css pixels.
21156
+ *
21157
+ * @method Html5#videoHeight
21158
+ * @return {number}
21159
+ * The value of `videoHeight` from the video element. This will be a number
21160
+ * in css pixels.
21161
+ *
21162
+ * @see [Spec] {@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-video-videowidth}
21163
+ */
21164
+ 'videoHeight'].forEach(function (prop) {
21165
+ Html5.prototype[prop] = function () {
21166
+ return this.el_[prop];
21167
+ };
21168
+ });
21169
+
21170
+ // Wrap native properties with a setter in this format:
21171
+ // set + toTitleCase(name)
21172
+ // The list is as follows:
21173
+ // setVolume, setSrc, setPoster, setPreload, setPlaybackRate, setDefaultPlaybackRate
21174
+ [
21175
+ /**
21176
+ * Set the value of `volume` on the media element. `volume` indicates the current
21177
+ * audio level as a percentage in decimal form. This means that 1 is 100%, 0.5 is 50%, and
21178
+ * so on.
21179
+ *
21180
+ * @method Html5#setVolume
21181
+ * @param {number} percentAsDecimal
21182
+ * The volume percent as a decimal. Valid range is from 0-1.
21183
+ *
21184
+ * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-a-volume}
21185
+ */
21186
+ 'volume',
21187
+
21188
+ /**
21189
+ * Set the value of `src` on the media element. `src` indicates the current
21190
+ * {@link Tech~SourceObject} for the media.
21191
+ *
21192
+ * @method Html5#setSrc
21193
+ * @param {Tech~SourceObject} src
21194
+ * The source object to set as the current source.
21195
+ *
21196
+ * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-src}
21197
+ */
21198
+ 'src',
21199
+
21200
+ /**
21201
+ * Set the value of `poster` on the media element. `poster` is the url to
21202
+ * an image file that can/will be shown when no media data is available.
21203
+ *
21204
+ * @method Html5#setPoster
21205
+ * @param {string} poster
21206
+ * The url to an image that should be used as the `poster` for the media
21207
+ * element.
21208
+ *
21209
+ * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-poster}
21210
+ */
21211
+ 'poster',
21212
+
21213
+ /**
21214
+ * Set the value of `preload` on the media element. `preload` indicates
21215
+ * what should download before the media is interacted with. It can have the following
21216
+ * values:
21217
+ * - none: nothing should be downloaded
21218
+ * - metadata: poster and the first few frames of the media may be downloaded to get
21219
+ * media dimensions and other metadata
21220
+ * - auto: allow the media and metadata for the media to be downloaded before
21221
+ * interaction
21222
+ *
21223
+ * @method Html5#setPreload
21224
+ * @param {string} preload
21225
+ * The value of `preload` to set on the media element. Must be 'none', 'metadata',
21226
+ * or 'auto'.
21227
+ *
21228
+ * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#attr-media-preload}
21229
+ */
21230
+ 'preload',
21231
+
21232
+ /**
21233
+ * Set the value of `playbackRate` on the media element. `playbackRate` indicates
21234
+ * the rate at which the media should play back. Examples:
21235
+ * - if playbackRate is set to 2, media will play twice as fast.
21236
+ * - if playbackRate is set to 0.5, media will play half as fast.
21237
+ *
21238
+ * @method Html5#setPlaybackRate
21239
+ * @return {number}
21240
+ * The value of `playbackRate` from the media element. A number indicating
21241
+ * the current playback speed of the media, where 1 is normal speed.
21242
+ *
21243
+ * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-playbackrate}
21244
+ */
21245
+ 'playbackRate',
21246
+
21247
+ /**
21248
+ * Set the value of `defaultPlaybackRate` on the media element. `defaultPlaybackRate` indicates
21249
+ * the rate at which the media should play back upon initial startup. Changing this value
21250
+ * after a video has started will do nothing. Instead you should used {@link Html5#setPlaybackRate}.
21251
+ *
21252
+ * Example Values:
21253
+ * - if playbackRate is set to 2, media will play twice as fast.
21254
+ * - if playbackRate is set to 0.5, media will play half as fast.
21255
+ *
21256
+ * @method Html5.prototype.setDefaultPlaybackRate
21257
+ * @return {number}
21258
+ * The value of `defaultPlaybackRate` from the media element. A number indicating
21259
+ * the current playback speed of the media, where 1 is normal speed.
21260
+ *
21261
+ * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-defaultplaybackrate}
21262
+ */
21263
+ 'defaultPlaybackRate'].forEach(function (prop) {
21264
+ Html5.prototype['set' + toTitleCase(prop)] = function (v) {
21265
+ this.el_[prop] = v;
21266
+ };
21267
+ });
21268
+
21269
+ // wrap native functions with a function
21270
+ // The list is as follows:
21271
+ // pause, load, play
21272
+ [
21273
+ /**
21274
+ * A wrapper around the media elements `pause` function. This will call the `HTML5`
21275
+ * media elements `pause` function.
21276
+ *
21277
+ * @method Html5#pause
21278
+ * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-pause}
21279
+ */
21280
+ 'pause',
21281
+
21282
+ /**
21283
+ * A wrapper around the media elements `load` function. This will call the `HTML5`s
21284
+ * media element `load` function.
21285
+ *
21286
+ * @method Html5#load
21287
+ * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-load}
21288
+ */
21289
+ 'load',
21290
+
21291
+ /**
21292
+ * A wrapper around the media elements `play` function. This will call the `HTML5`s
21293
+ * media element `play` function.
21294
+ *
21295
+ * @method Html5#play
21296
+ * @see [Spec]{@link https://www.w3.org/TR/html5/embedded-content-0.html#dom-media-play}
21297
+ */
21298
+ 'play'].forEach(function (prop) {
21299
+ Html5.prototype[prop] = function () {
21300
+ return this.el_[prop]();
21301
+ };
21302
+ });
21303
+
21304
+ Tech.withSourceHandlers(Html5);
21305
+
21306
+ /**
21307
+ * Native source handler for Html5, simply passes the source to the media element.
21308
+ *
21309
+ * @property {Tech~SourceObject} source
21310
+ * The source object
21311
+ *
21312
+ * @property {Html5} tech
21313
+ * The instance of the HTML5 tech.
21314
+ */
21315
+ Html5.nativeSourceHandler = {};
21316
+
21317
+ /**
21318
+ * Check if the media element can play the given mime type.
21319
+ *
21320
+ * @param {string} type
21321
+ * The mimetype to check
21322
+ *
21323
+ * @return {string}
21324
+ * 'probably', 'maybe', or '' (empty string)
21325
+ */
21326
+ Html5.nativeSourceHandler.canPlayType = function (type) {
21327
+ // IE without MediaPlayer throws an error (#519)
21328
+ try {
21329
+ return Html5.TEST_VID.canPlayType(type);
21330
+ } catch (e) {
21331
+ return '';
21332
+ }
21333
+ };
21334
+
21335
+ /**
21336
+ * Check if the media element can handle a source natively.
21337
+ *
21338
+ * @param {Tech~SourceObject} source
21339
+ * The source object
21340
+ *
21341
+ * @param {Object} [options]
21342
+ * Options to be passed to the tech.
21343
+ *
21344
+ * @return {string}
21345
+ * 'probably', 'maybe', or '' (empty string).
21346
+ */
21347
+ Html5.nativeSourceHandler.canHandleSource = function (source, options) {
21348
+
21349
+ // If a type was provided we should rely on that
21350
+ if (source.type) {
21351
+ return Html5.nativeSourceHandler.canPlayType(source.type);
21352
+
21353
+ // If no type, fall back to checking 'video/[EXTENSION]'
21354
+ } else if (source.src) {
21355
+ var ext = getFileExtension(source.src);
21356
+
21357
+ return Html5.nativeSourceHandler.canPlayType('video/' + ext);
21358
+ }
21359
+
21360
+ return '';
21361
+ };
21362
+
21363
+ /**
21364
+ * Pass the source to the native media element.
21365
+ *
21366
+ * @param {Tech~SourceObject} source
21367
+ * The source object
21368
+ *
21369
+ * @param {Html5} tech
21370
+ * The instance of the Html5 tech
21371
+ *
21372
+ * @param {Object} [options]
21373
+ * The options to pass to the source
21374
+ */
21375
+ Html5.nativeSourceHandler.handleSource = function (source, tech, options) {
21376
+ tech.setSrc(source.src);
21377
+ };
21378
+
21379
+ /**
21380
+ * A noop for the native dispose function, as cleanup is not needed.
21381
+ */
21382
+ Html5.nativeSourceHandler.dispose = function () {};
21383
+
21384
+ // Register the native source handler
21385
+ Html5.registerSourceHandler(Html5.nativeSourceHandler);
21386
+
21387
+ Tech.registerTech('Html5', Html5);
21388
+
21389
+ var _templateObject$2 = taggedTemplateLiteralLoose(['\n Using the tech directly can be dangerous. I hope you know what you\'re doing.\n See https://github.com/videojs/video.js/issues/2617 for more info.\n '], ['\n Using the tech directly can be dangerous. I hope you know what you\'re doing.\n See https://github.com/videojs/video.js/issues/2617 for more info.\n ']);
21390
+
21391
+ // The following tech events are simply re-triggered
21392
+ // on the player when they happen
21393
+ var TECH_EVENTS_RETRIGGER = [
21394
+ /**
21395
+ * Fired while the user agent is downloading media data.
21396
+ *
21397
+ * @event Player#progress
21398
+ * @type {EventTarget~Event}
21399
+ */
21400
+ /**
21401
+ * Retrigger the `progress` event that was triggered by the {@link Tech}.
21402
+ *
21403
+ * @private
21404
+ * @method Player#handleTechProgress_
21405
+ * @fires Player#progress
21406
+ * @listens Tech#progress
21407
+ */
21408
+ 'progress',
21409
+
21410
+ /**
21411
+ * Fires when the loading of an audio/video is aborted.
21412
+ *
21413
+ * @event Player#abort
21414
+ * @type {EventTarget~Event}
21415
+ */
21416
+ /**
21417
+ * Retrigger the `abort` event that was triggered by the {@link Tech}.
21418
+ *
21419
+ * @private
21420
+ * @method Player#handleTechAbort_
21421
+ * @fires Player#abort
21422
+ * @listens Tech#abort
21423
+ */
21424
+ 'abort',
21425
+
21426
+ /**
21427
+ * Fires when the browser is intentionally not getting media data.
21428
+ *
21429
+ * @event Player#suspend
21430
+ * @type {EventTarget~Event}
21431
+ */
21432
+ /**
21433
+ * Retrigger the `suspend` event that was triggered by the {@link Tech}.
21434
+ *
21435
+ * @private
21436
+ * @method Player#handleTechSuspend_
21437
+ * @fires Player#suspend
21438
+ * @listens Tech#suspend
21439
+ */
21440
+ 'suspend',
21441
+
21442
+ /**
21443
+ * Fires when the current playlist is empty.
21444
+ *
21445
+ * @event Player#emptied
21446
+ * @type {EventTarget~Event}
21447
+ */
21448
+ /**
21449
+ * Retrigger the `emptied` event that was triggered by the {@link Tech}.
21450
+ *
21451
+ * @private
21452
+ * @method Player#handleTechEmptied_
21453
+ * @fires Player#emptied
21454
+ * @listens Tech#emptied
21455
+ */
21456
+ 'emptied',
21457
+ /**
21458
+ * Fires when the browser is trying to get media data, but data is not available.
21459
+ *
21460
+ * @event Player#stalled
21461
+ * @type {EventTarget~Event}
21462
+ */
21463
+ /**
21464
+ * Retrigger the `stalled` event that was triggered by the {@link Tech}.
21465
+ *
21466
+ * @private
21467
+ * @method Player#handleTechStalled_
21468
+ * @fires Player#stalled
21469
+ * @listens Tech#stalled
21470
+ */
21471
+ 'stalled',
21472
+
21473
+ /**
21474
+ * Fires when the browser has loaded meta data for the audio/video.
21475
+ *
21476
+ * @event Player#loadedmetadata
21477
+ * @type {EventTarget~Event}
21478
+ */
21479
+ /**
21480
+ * Retrigger the `stalled` event that was triggered by the {@link Tech}.
21481
+ *
21482
+ * @private
21483
+ * @method Player#handleTechLoadedmetadata_
21484
+ * @fires Player#loadedmetadata
21485
+ * @listens Tech#loadedmetadata
21486
+ */
21487
+ 'loadedmetadata',
21488
+
21489
+ /**
21490
+ * Fires when the browser has loaded the current frame of the audio/video.
21491
+ *
21492
+ * @event Player#loadeddata
21493
+ * @type {event}
21494
+ */
21495
+ /**
21496
+ * Retrigger the `loadeddata` event that was triggered by the {@link Tech}.
21497
+ *
21498
+ * @private
21499
+ * @method Player#handleTechLoaddeddata_
21500
+ * @fires Player#loadeddata
21501
+ * @listens Tech#loadeddata
21502
+ */
21503
+ 'loadeddata',
21504
+
21505
+ /**
21506
+ * Fires when the current playback position has changed.
21507
+ *
21508
+ * @event Player#timeupdate
21509
+ * @type {event}
21510
+ */
21511
+ /**
21512
+ * Retrigger the `timeupdate` event that was triggered by the {@link Tech}.
21513
+ *
21514
+ * @private
21515
+ * @method Player#handleTechTimeUpdate_
21516
+ * @fires Player#timeupdate
21517
+ * @listens Tech#timeupdate
21518
+ */
21519
+ 'timeupdate',
21520
+
21521
+ /**
21522
+ * Fires when the video's intrinsic dimensions change
21523
+ *
21524
+ * @event Player#resize
21525
+ * @type {event}
21526
+ */
21527
+ /**
21528
+ * Retrigger the `resize` event that was triggered by the {@link Tech}.
21529
+ *
21530
+ * @private
21531
+ * @method Player#handleTechResize_
21532
+ * @fires Player#resize
21533
+ * @listens Tech#resize
21534
+ */
21535
+ 'resize',
21536
+
21537
+ /**
21538
+ * Fires when the volume has been changed
21539
+ *
21540
+ * @event Player#volumechange
21541
+ * @type {event}
21542
+ */
21543
+ /**
21544
+ * Retrigger the `volumechange` event that was triggered by the {@link Tech}.
21545
+ *
21546
+ * @private
21547
+ * @method Player#handleTechVolumechange_
21548
+ * @fires Player#volumechange
21549
+ * @listens Tech#volumechange
21550
+ */
21551
+ 'volumechange',
21552
+
21553
+ /**
21554
+ * Fires when the text track has been changed
21555
+ *
21556
+ * @event Player#texttrackchange
21557
+ * @type {event}
21558
+ */
21559
+ /**
21560
+ * Retrigger the `texttrackchange` event that was triggered by the {@link Tech}.
21561
+ *
21562
+ * @private
21563
+ * @method Player#handleTechTexttrackchange_
21564
+ * @fires Player#texttrackchange
21565
+ * @listens Tech#texttrackchange
21566
+ */
21567
+ 'texttrackchange'];
21568
+
21569
+ // events to queue when playback rate is zero
21570
+ // this is a hash for the sole purpose of mapping non-camel-cased event names
21571
+ // to camel-cased function names
21572
+ var TECH_EVENTS_QUEUE = {
21573
+ canplay: 'CanPlay',
21574
+ canplaythrough: 'CanPlayThrough',
21575
+ playing: 'Playing',
21576
+ seeked: 'Seeked'
21577
+ };
21578
+
21579
+ /**
21580
+ * An instance of the `Player` class is created when any of the Video.js setup methods
21581
+ * are used to initialize a video.
21582
+ *
21583
+ * After an instance has been created it can be accessed globally in two ways:
21584
+ * 1. By calling `videojs('example_video_1');`
21585
+ * 2. By using it directly via `videojs.players.example_video_1;`
21586
+ *
21587
+ * @extends Component
21588
+ */
21589
+
21590
+ var Player = function (_Component) {
21591
+ inherits(Player, _Component);
21592
+
21593
+ /**
21594
+ * Create an instance of this class.
21595
+ *
21596
+ * @param {Element} tag
21597
+ * The original video DOM element used for configuring options.
21598
+ *
21599
+ * @param {Object} [options]
21600
+ * Object of option names and values.
21601
+ *
21602
+ * @param {Component~ReadyCallback} [ready]
21603
+ * Ready callback function.
21604
+ */
21605
+ function Player(tag, options, ready) {
21606
+ classCallCheck(this, Player);
21607
+
21608
+ // Make sure tag ID exists
21609
+ tag.id = tag.id || options.id || 'vjs_video_' + newGUID();
21610
+
21611
+ // Set Options
21612
+ // The options argument overrides options set in the video tag
21613
+ // which overrides globally set options.
21614
+ // This latter part coincides with the load order
21615
+ // (tag must exist before Player)
21616
+ options = assign(Player.getTagSettings(tag), options);
21617
+
21618
+ // Delay the initialization of children because we need to set up
21619
+ // player properties first, and can't use `this` before `super()`
21620
+ options.initChildren = false;
21621
+
21622
+ // Same with creating the element
21623
+ options.createEl = false;
21624
+
21625
+ // don't auto mixin the evented mixin
21626
+ options.evented = false;
21627
+
21628
+ // we don't want the player to report touch activity on itself
21629
+ // see enableTouchActivity in Component
21630
+ options.reportTouchActivity = false;
21631
+
21632
+ // If language is not set, get the closest lang attribute
21633
+ if (!options.language) {
21634
+ if (typeof tag.closest === 'function') {
21635
+ var closest = tag.closest('[lang]');
21636
+
21637
+ if (closest && closest.getAttribute) {
21638
+ options.language = closest.getAttribute('lang');
21639
+ }
21640
+ } else {
21641
+ var element = tag;
21642
+
21643
+ while (element && element.nodeType === 1) {
21644
+ if (getAttributes(element).hasOwnProperty('lang')) {
21645
+ options.language = element.getAttribute('lang');
21646
+ break;
21647
+ }
21648
+ element = element.parentNode;
21649
+ }
21650
+ }
21651
+ }
21652
+
21653
+ // Run base component initializing with new options
21654
+
21655
+ // Tracks when a tech changes the poster
21656
+ var _this = possibleConstructorReturn(this, _Component.call(this, null, options, ready));
21657
+
21658
+ _this.isPosterFromTech_ = false;
21659
+
21660
+ // Holds callback info that gets queued when playback rate is zero
21661
+ // and a seek is happening
21662
+ _this.queuedCallbacks_ = [];
21663
+
21664
+ // Turn off API access because we're loading a new tech that might load asynchronously
21665
+ _this.isReady_ = false;
21666
+
21667
+ // Init state hasStarted_
21668
+ _this.hasStarted_ = false;
21669
+
21670
+ // Init state userActive_
21671
+ _this.userActive_ = false;
21672
+
21673
+ // if the global option object was accidentally blown away by
21674
+ // someone, bail early with an informative error
21675
+ if (!_this.options_ || !_this.options_.techOrder || !_this.options_.techOrder.length) {
21676
+ throw new Error('No techOrder specified. Did you overwrite ' + 'videojs.options instead of just changing the ' + 'properties you want to override?');
21677
+ }
21678
+
21679
+ // Store the original tag used to set options
21680
+ _this.tag = tag;
21681
+
21682
+ // Store the tag attributes used to restore html5 element
21683
+ _this.tagAttributes = tag && getAttributes(tag);
21684
+
21685
+ // Update current language
21686
+ _this.language(_this.options_.language);
21687
+
21688
+ // Update Supported Languages
21689
+ if (options.languages) {
21690
+ // Normalise player option languages to lowercase
21691
+ var languagesToLower = {};
21692
+
21693
+ Object.getOwnPropertyNames(options.languages).forEach(function (name$$1) {
21694
+ languagesToLower[name$$1.toLowerCase()] = options.languages[name$$1];
21695
+ });
21696
+ _this.languages_ = languagesToLower;
21697
+ } else {
21698
+ _this.languages_ = Player.prototype.options_.languages;
21699
+ }
21700
+
21701
+ // Cache for video property values.
21702
+ _this.cache_ = {};
21703
+
21704
+ // Set poster
21705
+ _this.poster_ = options.poster || '';
21706
+
21707
+ // Set controls
21708
+ _this.controls_ = !!options.controls;
21709
+
21710
+ // Set default values for lastVolume
21711
+ _this.cache_.lastVolume = 1;
21712
+
21713
+ // Original tag settings stored in options
21714
+ // now remove immediately so native controls don't flash.
21715
+ // May be turned back on by HTML5 tech if nativeControlsForTouch is true
21716
+ tag.controls = false;
21717
+ tag.removeAttribute('controls');
21718
+
21719
+ // the attribute overrides the option
21720
+ if (tag.hasAttribute('autoplay')) {
21721
+ _this.options_.autoplay = true;
21722
+ } else {
21723
+ // otherwise use the setter to validate and
21724
+ // set the correct value.
21725
+ _this.autoplay(_this.options_.autoplay);
21726
+ }
21727
+
21728
+ /*
21729
+ * Store the internal state of scrubbing
21730
+ *
21731
+ * @private
21732
+ * @return {Boolean} True if the user is scrubbing
21733
+ */
21734
+ _this.scrubbing_ = false;
21735
+
21736
+ _this.el_ = _this.createEl();
21737
+
21738
+ // Set default value for lastPlaybackRate
21739
+ _this.cache_.lastPlaybackRate = _this.defaultPlaybackRate();
21740
+
21741
+ // Make this an evented object and use `el_` as its event bus.
21742
+ evented(_this, { eventBusKey: 'el_' });
21743
+
21744
+ // We also want to pass the original player options to each component and plugin
21745
+ // as well so they don't need to reach back into the player for options later.
21746
+ // We also need to do another copy of this.options_ so we don't end up with
21747
+ // an infinite loop.
21748
+ var playerOptionsCopy = mergeOptions(_this.options_);
21749
+
21750
+ // Load plugins
21751
+ if (options.plugins) {
21752
+ var plugins = options.plugins;
21753
+
21754
+ Object.keys(plugins).forEach(function (name$$1) {
21755
+ if (typeof this[name$$1] === 'function') {
21756
+ this[name$$1](plugins[name$$1]);
21757
+ } else {
21758
+ throw new Error('plugin "' + name$$1 + '" does not exist');
21759
+ }
21760
+ }, _this);
21761
+ }
21762
+
21763
+ _this.options_.playerOptions = playerOptionsCopy;
21764
+
21765
+ _this.middleware_ = [];
21766
+
21767
+ _this.initChildren();
21768
+
21769
+ // Set isAudio based on whether or not an audio tag was used
21770
+ _this.isAudio(tag.nodeName.toLowerCase() === 'audio');
21771
+
21772
+ // Update controls className. Can't do this when the controls are initially
21773
+ // set because the element doesn't exist yet.
21774
+ if (_this.controls()) {
21775
+ _this.addClass('vjs-controls-enabled');
21776
+ } else {
21777
+ _this.addClass('vjs-controls-disabled');
21778
+ }
21779
+
21780
+ // Set ARIA label and region role depending on player type
21781
+ _this.el_.setAttribute('role', 'region');
21782
+ if (_this.isAudio()) {
21783
+ _this.el_.setAttribute('aria-label', _this.localize('Audio Player'));
21784
+ } else {
21785
+ _this.el_.setAttribute('aria-label', _this.localize('Video Player'));
21786
+ }
21787
+
21788
+ if (_this.isAudio()) {
21789
+ _this.addClass('vjs-audio');
21790
+ }
21791
+
21792
+ if (_this.flexNotSupported_()) {
21793
+ _this.addClass('vjs-no-flex');
21794
+ }
21795
+
21796
+ // TODO: Make this smarter. Toggle user state between touching/mousing
21797
+ // using events, since devices can have both touch and mouse events.
21798
+ // if (browser.TOUCH_ENABLED) {
21799
+ // this.addClass('vjs-touch-enabled');
21800
+ // }
21801
+
21802
+ // iOS Safari has broken hover handling
21803
+ if (!IS_IOS) {
21804
+ _this.addClass('vjs-workinghover');
21805
+ }
21806
+
21807
+ // Make player easily findable by ID
21808
+ Player.players[_this.id_] = _this;
21809
+
21810
+ // Add a major version class to aid css in plugins
21811
+ var majorVersion = version.split('.')[0];
21812
+
21813
+ _this.addClass('vjs-v' + majorVersion);
21814
+
21815
+ // When the player is first initialized, trigger activity so components
21816
+ // like the control bar show themselves if needed
21817
+ _this.userActive(true);
21818
+ _this.reportUserActivity();
21819
+
21820
+ _this.one('play', _this.listenForUserActivity_);
21821
+ _this.on('fullscreenchange', _this.handleFullscreenChange_);
21822
+ _this.on('stageclick', _this.handleStageClick_);
21823
+
21824
+ _this.changingSrc_ = false;
21825
+ _this.playWaitingForReady_ = false;
21826
+ _this.playOnLoadstart_ = null;
21827
+ return _this;
21828
+ }
21829
+
21830
+ /**
21831
+ * Destroys the video player and does any necessary cleanup.
21832
+ *
21833
+ * This is especially helpful if you are dynamically adding and removing videos
21834
+ * to/from the DOM.
21835
+ *
21836
+ * @fires Player#dispose
21837
+ */
21838
+
21839
+
21840
+ Player.prototype.dispose = function dispose() {
21841
+ /**
21842
+ * Called when the player is being disposed of.
21843
+ *
21844
+ * @event Player#dispose
21845
+ * @type {EventTarget~Event}
21846
+ */
21847
+ this.trigger('dispose');
21848
+ // prevent dispose from being called twice
21849
+ this.off('dispose');
21850
+
21851
+ if (this.styleEl_ && this.styleEl_.parentNode) {
21852
+ this.styleEl_.parentNode.removeChild(this.styleEl_);
21853
+ this.styleEl_ = null;
21854
+ }
21855
+
21856
+ // Kill reference to this player
21857
+ Player.players[this.id_] = null;
21858
+
21859
+ if (this.tag && this.tag.player) {
21860
+ this.tag.player = null;
21861
+ }
21862
+
21863
+ if (this.el_ && this.el_.player) {
21864
+ this.el_.player = null;
21865
+ }
21866
+
21867
+ if (this.tech_) {
21868
+ this.tech_.dispose();
21869
+ this.isPosterFromTech_ = false;
21870
+ this.poster_ = '';
21871
+ }
21872
+
21873
+ if (this.playerElIngest_) {
21874
+ this.playerElIngest_ = null;
21875
+ }
21876
+
21877
+ if (this.tag) {
21878
+ this.tag = null;
21879
+ }
21880
+
21881
+ clearCacheForPlayer(this);
21882
+
21883
+ // the actual .el_ is removed here
21884
+ _Component.prototype.dispose.call(this);
21885
+ };
21886
+
21887
+ /**
21888
+ * Create the `Player`'s DOM element.
21889
+ *
21890
+ * @return {Element}
21891
+ * The DOM element that gets created.
21892
+ */
21893
+
21894
+
21895
+ Player.prototype.createEl = function createEl$$1() {
21896
+ var tag = this.tag;
21897
+ var el = void 0;
21898
+ var playerElIngest = this.playerElIngest_ = tag.parentNode && tag.parentNode.hasAttribute && tag.parentNode.hasAttribute('data-vjs-player');
21899
+ var divEmbed = this.tag.tagName.toLowerCase() === 'video-js';
21900
+
21901
+ if (playerElIngest) {
21902
+ el = this.el_ = tag.parentNode;
21903
+ } else if (!divEmbed) {
21904
+ el = this.el_ = _Component.prototype.createEl.call(this, 'div');
21905
+ }
21906
+
21907
+ // Copy over all the attributes from the tag, including ID and class
21908
+ // ID will now reference player box, not the video tag
21909
+ var attrs = getAttributes(tag);
21910
+
21911
+ if (divEmbed) {
21912
+ el = this.el_ = tag;
21913
+ tag = this.tag = document_1.createElement('video');
21914
+ while (el.children.length) {
21915
+ tag.appendChild(el.firstChild);
21916
+ }
21917
+
21918
+ if (!hasClass(el, 'video-js')) {
21919
+ addClass(el, 'video-js');
21920
+ }
21921
+
21922
+ el.appendChild(tag);
21923
+
21924
+ playerElIngest = this.playerElIngest_ = el;
21925
+ // move properties over from our custom `video-js` element
21926
+ // to our new `video` element. This will move things like
21927
+ // `src` or `controls` that were set via js before the player
21928
+ // was initialized.
21929
+ Object.keys(el).forEach(function (k) {
21930
+ tag[k] = el[k];
21931
+ });
21932
+ }
21933
+
21934
+ // set tabindex to -1 to remove the video element from the focus order
21935
+ tag.setAttribute('tabindex', '-1');
21936
+ // Workaround for #4583 (JAWS+IE doesn't announce BPB or play button)
21937
+ // See https://github.com/FreedomScientific/VFO-standards-support/issues/78
21938
+ // Note that we can't detect if JAWS is being used, but this ARIA attribute
21939
+ // doesn't change behavior of IE11 if JAWS is not being used
21940
+ if (IE_VERSION) {
21941
+ tag.setAttribute('role', 'application');
21942
+ }
21943
+
21944
+ // Remove width/height attrs from tag so CSS can make it 100% width/height
21945
+ tag.removeAttribute('width');
21946
+ tag.removeAttribute('height');
21947
+
21948
+ Object.getOwnPropertyNames(attrs).forEach(function (attr) {
21949
+ // don't copy over the class attribute to the player element when we're in a div embed
21950
+ // the class is already set up properly in the divEmbed case
21951
+ // and we want to make sure that the `video-js` class doesn't get lost
21952
+ if (!(divEmbed && attr === 'class')) {
21953
+ el.setAttribute(attr, attrs[attr]);
21954
+ }
21955
+
21956
+ if (divEmbed) {
21957
+ tag.setAttribute(attr, attrs[attr]);
21958
+ }
21959
+ });
21960
+
21961
+ // Update tag id/class for use as HTML5 playback tech
21962
+ // Might think we should do this after embedding in container so .vjs-tech class
21963
+ // doesn't flash 100% width/height, but class only applies with .video-js parent
21964
+ tag.playerId = tag.id;
21965
+ tag.id += '_html5_api';
21966
+ tag.className = 'vjs-tech';
21967
+
21968
+ // Make player findable on elements
21969
+ tag.player = el.player = this;
21970
+ // Default state of video is paused
21971
+ this.addClass('vjs-paused');
21972
+
21973
+ // Add a style element in the player that we'll use to set the width/height
21974
+ // of the player in a way that's still overrideable by CSS, just like the
21975
+ // video element
21976
+ if (window_1.VIDEOJS_NO_DYNAMIC_STYLE !== true) {
21977
+ this.styleEl_ = createStyleElement('vjs-styles-dimensions');
21978
+ var defaultsStyleEl = $('.vjs-styles-defaults');
21979
+ var head = $('head');
21980
+
21981
+ head.insertBefore(this.styleEl_, defaultsStyleEl ? defaultsStyleEl.nextSibling : head.firstChild);
21982
+ }
21983
+
21984
+ // Pass in the width/height/aspectRatio options which will update the style el
21985
+ this.width(this.options_.width);
21986
+ this.height(this.options_.height);
21987
+ this.fluid(this.options_.fluid);
21988
+ this.aspectRatio(this.options_.aspectRatio);
21989
+
21990
+ // Hide any links within the video/audio tag,
21991
+ // because IE doesn't hide them completely from screen readers.
21992
+ var links = tag.getElementsByTagName('a');
21993
+
21994
+ for (var i = 0; i < links.length; i++) {
21995
+ var linkEl = links.item(i);
21996
+
21997
+ addClass(linkEl, 'vjs-hidden');
21998
+ linkEl.setAttribute('hidden', 'hidden');
21999
+ }
22000
+
22001
+ // insertElFirst seems to cause the networkState to flicker from 3 to 2, so
22002
+ // keep track of the original for later so we can know if the source originally failed
22003
+ tag.initNetworkState_ = tag.networkState;
22004
+
22005
+ // Wrap video tag in div (el/box) container
22006
+ if (tag.parentNode && !playerElIngest) {
22007
+ tag.parentNode.insertBefore(el, tag);
22008
+ }
22009
+
22010
+ // insert the tag as the first child of the player element
22011
+ // then manually add it to the children array so that this.addChild
22012
+ // will work properly for other components
22013
+ //
22014
+ // Breaks iPhone, fixed in HTML5 setup.
22015
+ prependTo(tag, el);
22016
+ this.children_.unshift(tag);
22017
+
22018
+ // Set lang attr on player to ensure CSS :lang() in consistent with player
22019
+ // if it's been set to something different to the doc
22020
+ this.el_.setAttribute('lang', this.language_);
22021
+
22022
+ this.el_ = el;
22023
+
22024
+ return el;
22025
+ };
22026
+
22027
+ /**
22028
+ * A getter/setter for the `Player`'s width. Returns the player's configured value.
22029
+ * To get the current width use `currentWidth()`.
22030
+ *
22031
+ * @param {number} [value]
22032
+ * The value to set the `Player`'s width to.
22033
+ *
22034
+ * @return {number}
22035
+ * The current width of the `Player` when getting.
22036
+ */
22037
+
22038
+
22039
+ Player.prototype.width = function width(value) {
22040
+ return this.dimension('width', value);
22041
+ };
22042
+
22043
+ /**
22044
+ * A getter/setter for the `Player`'s height. Returns the player's configured value.
22045
+ * To get the current height use `currentheight()`.
22046
+ *
22047
+ * @param {number} [value]
22048
+ * The value to set the `Player`'s heigth to.
22049
+ *
22050
+ * @return {number}
22051
+ * The current height of the `Player` when getting.
22052
+ */
22053
+
22054
+
22055
+ Player.prototype.height = function height(value) {
22056
+ return this.dimension('height', value);
22057
+ };
22058
+
22059
+ /**
22060
+ * A getter/setter for the `Player`'s width & height.
22061
+ *
22062
+ * @param {string} dimension
22063
+ * This string can be:
22064
+ * - 'width'
22065
+ * - 'height'
22066
+ *
22067
+ * @param {number} [value]
22068
+ * Value for dimension specified in the first argument.
22069
+ *
22070
+ * @return {number}
22071
+ * The dimension arguments value when getting (width/height).
22072
+ */
22073
+
22074
+
22075
+ Player.prototype.dimension = function dimension(_dimension, value) {
22076
+ var privDimension = _dimension + '_';
22077
+
22078
+ if (value === undefined) {
22079
+ return this[privDimension] || 0;
22080
+ }
22081
+
22082
+ if (value === '') {
22083
+ // If an empty string is given, reset the dimension to be automatic
22084
+ this[privDimension] = undefined;
22085
+ this.updateStyleEl_();
22086
+ return;
22087
+ }
22088
+
22089
+ var parsedVal = parseFloat(value);
22090
+
22091
+ if (isNaN(parsedVal)) {
22092
+ log$1.error('Improper value "' + value + '" supplied for for ' + _dimension);
22093
+ return;
22094
+ }
22095
+
22096
+ this[privDimension] = parsedVal;
22097
+ this.updateStyleEl_();
22098
+ };
22099
+
22100
+ /**
22101
+ * A getter/setter/toggler for the vjs-fluid `className` on the `Player`.
22102
+ *
22103
+ * @param {boolean} [bool]
22104
+ * - A value of true adds the class.
22105
+ * - A value of false removes the class.
22106
+ * - No value will toggle the fluid class.
22107
+ *
22108
+ * @return {boolean|undefined}
22109
+ * - The value of fluid when getting.
22110
+ * - `undefined` when setting.
22111
+ */
22112
+
22113
+
22114
+ Player.prototype.fluid = function fluid(bool) {
22115
+ if (bool === undefined) {
22116
+ return !!this.fluid_;
22117
+ }
22118
+
22119
+ this.fluid_ = !!bool;
22120
+
22121
+ if (bool) {
22122
+ this.addClass('vjs-fluid');
22123
+ } else {
22124
+ this.removeClass('vjs-fluid');
22125
+ }
22126
+
22127
+ this.updateStyleEl_();
22128
+ };
22129
+
22130
+ /**
22131
+ * Get/Set the aspect ratio
22132
+ *
22133
+ * @param {string} [ratio]
22134
+ * Aspect ratio for player
22135
+ *
22136
+ * @return {string|undefined}
22137
+ * returns the current aspect ratio when getting
22138
+ */
22139
+
22140
+ /**
22141
+ * A getter/setter for the `Player`'s aspect ratio.
22142
+ *
22143
+ * @param {string} [ratio]
22144
+ * The value to set the `Player's aspect ratio to.
22145
+ *
22146
+ * @return {string|undefined}
22147
+ * - The current aspect ratio of the `Player` when getting.
22148
+ * - undefined when setting
22149
+ */
22150
+
22151
+
22152
+ Player.prototype.aspectRatio = function aspectRatio(ratio) {
22153
+ if (ratio === undefined) {
22154
+ return this.aspectRatio_;
22155
+ }
22156
+
22157
+ // Check for width:height format
22158
+ if (!/^\d+\:\d+$/.test(ratio)) {
22159
+ throw new Error('Improper value supplied for aspect ratio. The format should be width:height, for example 16:9.');
22160
+ }
22161
+ this.aspectRatio_ = ratio;
22162
+
22163
+ // We're assuming if you set an aspect ratio you want fluid mode,
22164
+ // because in fixed mode you could calculate width and height yourself.
22165
+ this.fluid(true);
22166
+
22167
+ this.updateStyleEl_();
22168
+ };
22169
+
22170
+ /**
22171
+ * Update styles of the `Player` element (height, width and aspect ratio).
22172
+ *
22173
+ * @private
22174
+ * @listens Tech#loadedmetadata
22175
+ */
22176
+
22177
+
22178
+ Player.prototype.updateStyleEl_ = function updateStyleEl_() {
22179
+ if (window_1.VIDEOJS_NO_DYNAMIC_STYLE === true) {
22180
+ var _width = typeof this.width_ === 'number' ? this.width_ : this.options_.width;
22181
+ var _height = typeof this.height_ === 'number' ? this.height_ : this.options_.height;
22182
+ var techEl = this.tech_ && this.tech_.el();
22183
+
22184
+ if (techEl) {
22185
+ if (_width >= 0) {
22186
+ techEl.width = _width;
22187
+ }
22188
+ if (_height >= 0) {
22189
+ techEl.height = _height;
22190
+ }
22191
+ }
22192
+
22193
+ return;
22194
+ }
22195
+
22196
+ var width = void 0;
22197
+ var height = void 0;
22198
+ var aspectRatio = void 0;
22199
+ var idClass = void 0;
22200
+
22201
+ // The aspect ratio is either used directly or to calculate width and height.
22202
+ if (this.aspectRatio_ !== undefined && this.aspectRatio_ !== 'auto') {
22203
+ // Use any aspectRatio that's been specifically set
22204
+ aspectRatio = this.aspectRatio_;
22205
+ } else if (this.videoWidth() > 0) {
22206
+ // Otherwise try to get the aspect ratio from the video metadata
22207
+ aspectRatio = this.videoWidth() + ':' + this.videoHeight();
22208
+ } else {
22209
+ // Or use a default. The video element's is 2:1, but 16:9 is more common.
22210
+ aspectRatio = '16:9';
22211
+ }
22212
+
22213
+ // Get the ratio as a decimal we can use to calculate dimensions
22214
+ var ratioParts = aspectRatio.split(':');
22215
+ var ratioMultiplier = ratioParts[1] / ratioParts[0];
22216
+
22217
+ if (this.width_ !== undefined) {
22218
+ // Use any width that's been specifically set
22219
+ width = this.width_;
22220
+ } else if (this.height_ !== undefined) {
22221
+ // Or calulate the width from the aspect ratio if a height has been set
22222
+ width = this.height_ / ratioMultiplier;
22223
+ } else {
22224
+ // Or use the video's metadata, or use the video el's default of 300
22225
+ width = this.videoWidth() || 300;
22226
+ }
22227
+
22228
+ if (this.height_ !== undefined) {
22229
+ // Use any height that's been specifically set
22230
+ height = this.height_;
22231
+ } else {
22232
+ // Otherwise calculate the height from the ratio and the width
22233
+ height = width * ratioMultiplier;
22234
+ }
22235
+
22236
+ // Ensure the CSS class is valid by starting with an alpha character
22237
+ if (/^[^a-zA-Z]/.test(this.id())) {
22238
+ idClass = 'dimensions-' + this.id();
22239
+ } else {
22240
+ idClass = this.id() + '-dimensions';
22241
+ }
22242
+
22243
+ // Ensure the right class is still on the player for the style element
22244
+ this.addClass(idClass);
22245
+
22246
+ setTextContent(this.styleEl_, '\n .' + idClass + ' {\n width: ' + width + 'px;\n height: ' + height + 'px;\n }\n\n .' + idClass + '.vjs-fluid {\n padding-top: ' + ratioMultiplier * 100 + '%;\n }\n ');
22247
+ };
22248
+
22249
+ /**
22250
+ * Load/Create an instance of playback {@link Tech} including element
22251
+ * and API methods. Then append the `Tech` element in `Player` as a child.
22252
+ *
22253
+ * @param {string} techName
22254
+ * name of the playback technology
22255
+ *
22256
+ * @param {string} source
22257
+ * video source
22258
+ *
22259
+ * @private
22260
+ */
22261
+
22262
+
22263
+ Player.prototype.loadTech_ = function loadTech_(techName, source) {
22264
+ var _this2 = this;
22265
+
22266
+ // Pause and remove current playback technology
22267
+ if (this.tech_) {
22268
+ this.unloadTech_();
22269
+ }
22270
+
22271
+ var titleTechName = toTitleCase(techName);
22272
+ var camelTechName = techName.charAt(0).toLowerCase() + techName.slice(1);
22273
+
22274
+ // get rid of the HTML5 video tag as soon as we are using another tech
22275
+ if (titleTechName !== 'Html5' && this.tag) {
22276
+ Tech.getTech('Html5').disposeMediaElement(this.tag);
22277
+ this.tag.player = null;
22278
+ this.tag = null;
22279
+ }
22280
+
22281
+ this.techName_ = titleTechName;
22282
+
22283
+ // Turn off API access because we're loading a new tech that might load asynchronously
22284
+ this.isReady_ = false;
22285
+
22286
+ // if autoplay is a string we pass false to the tech
22287
+ // because the player is going to handle autoplay on `loadstart`
22288
+ var autoplay = typeof this.autoplay() === 'string' ? false : this.autoplay();
22289
+
22290
+ // Grab tech-specific options from player options and add source and parent element to use.
22291
+ var techOptions = {
22292
+ source: source,
22293
+ autoplay: autoplay,
22294
+ 'nativeControlsForTouch': this.options_.nativeControlsForTouch,
22295
+ 'playerId': this.id(),
22296
+ 'techId': this.id() + '_' + titleTechName + '_api',
22297
+ 'playsinline': this.options_.playsinline,
22298
+ 'preload': this.options_.preload,
22299
+ 'loop': this.options_.loop,
22300
+ 'muted': this.options_.muted,
22301
+ 'poster': this.poster(),
22302
+ 'language': this.language(),
22303
+ 'playerElIngest': this.playerElIngest_ || false,
22304
+ 'vtt.js': this.options_['vtt.js'],
22305
+ 'canOverridePoster': !!this.options_.techCanOverridePoster,
22306
+ 'enableSourceset': this.options_.enableSourceset
22307
+ };
22308
+
22309
+ ALL.names.forEach(function (name$$1) {
22310
+ var props = ALL[name$$1];
22311
+
22312
+ techOptions[props.getterName] = _this2[props.privateName];
22313
+ });
22314
+
22315
+ assign(techOptions, this.options_[titleTechName]);
22316
+ assign(techOptions, this.options_[camelTechName]);
22317
+ assign(techOptions, this.options_[techName.toLowerCase()]);
22318
+
22319
+ if (this.tag) {
22320
+ techOptions.tag = this.tag;
22321
+ }
22322
+
22323
+ if (source && source.src === this.cache_.src && this.cache_.currentTime > 0) {
22324
+ techOptions.startTime = this.cache_.currentTime;
22325
+ }
22326
+
22327
+ // Initialize tech instance
22328
+ var TechClass = Tech.getTech(techName);
22329
+
22330
+ if (!TechClass) {
22331
+ throw new Error('No Tech named \'' + titleTechName + '\' exists! \'' + titleTechName + '\' should be registered using videojs.registerTech()\'');
22332
+ }
22333
+
22334
+ this.tech_ = new TechClass(techOptions);
22335
+
22336
+ // player.triggerReady is always async, so don't need this to be async
22337
+ this.tech_.ready(bind(this, this.handleTechReady_), true);
22338
+
22339
+ textTrackConverter.jsonToTextTracks(this.textTracksJson_ || [], this.tech_);
22340
+
22341
+ // Listen to all HTML5-defined events and trigger them on the player
22342
+ TECH_EVENTS_RETRIGGER.forEach(function (event) {
22343
+ _this2.on(_this2.tech_, event, _this2['handleTech' + toTitleCase(event) + '_']);
22344
+ });
22345
+
22346
+ Object.keys(TECH_EVENTS_QUEUE).forEach(function (event) {
22347
+ _this2.on(_this2.tech_, event, function (eventObj) {
22348
+ if (_this2.tech_.playbackRate() === 0 && _this2.tech_.seeking()) {
22349
+ _this2.queuedCallbacks_.push({
22350
+ callback: _this2['handleTech' + TECH_EVENTS_QUEUE[event] + '_'].bind(_this2),
22351
+ event: eventObj
22352
+ });
22353
+ return;
22354
+ }
22355
+ _this2['handleTech' + TECH_EVENTS_QUEUE[event] + '_'](eventObj);
22356
+ });
22357
+ });
22358
+
22359
+ this.on(this.tech_, 'loadstart', this.handleTechLoadStart_);
22360
+ this.on(this.tech_, 'sourceset', this.handleTechSourceset_);
22361
+ this.on(this.tech_, 'waiting', this.handleTechWaiting_);
22362
+ this.on(this.tech_, 'ended', this.handleTechEnded_);
22363
+ this.on(this.tech_, 'seeking', this.handleTechSeeking_);
22364
+ this.on(this.tech_, 'play', this.handleTechPlay_);
22365
+ this.on(this.tech_, 'firstplay', this.handleTechFirstPlay_);
22366
+ this.on(this.tech_, 'pause', this.handleTechPause_);
22367
+ this.on(this.tech_, 'durationchange', this.handleTechDurationChange_);
22368
+ this.on(this.tech_, 'fullscreenchange', this.handleTechFullscreenChange_);
22369
+ this.on(this.tech_, 'error', this.handleTechError_);
22370
+ this.on(this.tech_, 'loadedmetadata', this.updateStyleEl_);
22371
+ this.on(this.tech_, 'posterchange', this.handleTechPosterChange_);
22372
+ this.on(this.tech_, 'textdata', this.handleTechTextData_);
22373
+ this.on(this.tech_, 'ratechange', this.handleTechRateChange_);
22374
+
22375
+ this.usingNativeControls(this.techGet_('controls'));
22376
+
22377
+ if (this.controls() && !this.usingNativeControls()) {
22378
+ this.addTechControlsListeners_();
22379
+ }
22380
+
22381
+ // Add the tech element in the DOM if it was not already there
22382
+ // Make sure to not insert the original video element if using Html5
22383
+ if (this.tech_.el().parentNode !== this.el() && (titleTechName !== 'Html5' || !this.tag)) {
22384
+ prependTo(this.tech_.el(), this.el());
22385
+ }
22386
+
22387
+ // Get rid of the original video tag reference after the first tech is loaded
22388
+ if (this.tag) {
22389
+ this.tag.player = null;
22390
+ this.tag = null;
22391
+ }
22392
+ };
22393
+
22394
+ /**
22395
+ * Unload and dispose of the current playback {@link Tech}.
22396
+ *
22397
+ * @private
22398
+ */
22399
+
22400
+
22401
+ Player.prototype.unloadTech_ = function unloadTech_() {
22402
+ var _this3 = this;
22403
+
22404
+ // Save the current text tracks so that we can reuse the same text tracks with the next tech
22405
+ ALL.names.forEach(function (name$$1) {
22406
+ var props = ALL[name$$1];
22407
+
22408
+ _this3[props.privateName] = _this3[props.getterName]();
22409
+ });
22410
+ this.textTracksJson_ = textTrackConverter.textTracksToJson(this.tech_);
22411
+
22412
+ this.isReady_ = false;
22413
+
22414
+ this.tech_.dispose();
22415
+
22416
+ this.tech_ = false;
22417
+
22418
+ if (this.isPosterFromTech_) {
22419
+ this.poster_ = '';
22420
+ this.trigger('posterchange');
22421
+ }
22422
+
22423
+ this.isPosterFromTech_ = false;
22424
+ };
22425
+
22426
+ /**
22427
+ * Return a reference to the current {@link Tech}.
22428
+ * It will print a warning by default about the danger of using the tech directly
22429
+ * but any argument that is passed in will silence the warning.
22430
+ *
22431
+ * @param {*} [safety]
22432
+ * Anything passed in to silence the warning
22433
+ *
22434
+ * @return {Tech}
22435
+ * The Tech
22436
+ */
22437
+
22438
+
22439
+ Player.prototype.tech = function tech(safety) {
22440
+ if (safety === undefined) {
22441
+ log$1.warn(tsml(_templateObject$2));
22442
+ }
22443
+
22444
+ return this.tech_;
22445
+ };
22446
+
22447
+ /**
22448
+ * Set up click and touch listeners for the playback element
22449
+ *
22450
+ * - On desktops: a click on the video itself will toggle playback
22451
+ * - On mobile devices: a click on the video toggles controls
22452
+ * which is done by toggling the user state between active and
22453
+ * inactive
22454
+ * - A tap can signal that a user has become active or has become inactive
22455
+ * e.g. a quick tap on an iPhone movie should reveal the controls. Another
22456
+ * quick tap should hide them again (signaling the user is in an inactive
22457
+ * viewing state)
22458
+ * - In addition to this, we still want the user to be considered inactive after
22459
+ * a few seconds of inactivity.
22460
+ *
22461
+ * > Note: the only part of iOS interaction we can't mimic with this setup
22462
+ * is a touch and hold on the video element counting as activity in order to
22463
+ * keep the controls showing, but that shouldn't be an issue. A touch and hold
22464
+ * on any controls will still keep the user active
22465
+ *
22466
+ * @private
22467
+ */
22468
+
22469
+
22470
+ Player.prototype.addTechControlsListeners_ = function addTechControlsListeners_() {
22471
+ // Make sure to remove all the previous listeners in case we are called multiple times.
22472
+ this.removeTechControlsListeners_();
22473
+
22474
+ // Some browsers (Chrome & IE) don't trigger a click on a flash swf, but do
22475
+ // trigger mousedown/up.
22476
+ // http://stackoverflow.com/questions/1444562/javascript-onclick-event-over-flash-object
22477
+ // Any touch events are set to block the mousedown event from happening
22478
+ this.on(this.tech_, 'mousedown', this.handleTechClick_);
22479
+ this.on(this.tech_, 'dblclick', this.handleTechDoubleClick_);
22480
+
22481
+ // If the controls were hidden we don't want that to change without a tap event
22482
+ // so we'll check if the controls were already showing before reporting user
22483
+ // activity
22484
+ this.on(this.tech_, 'touchstart', this.handleTechTouchStart_);
22485
+ this.on(this.tech_, 'touchmove', this.handleTechTouchMove_);
22486
+ this.on(this.tech_, 'touchend', this.handleTechTouchEnd_);
22487
+
22488
+ // The tap listener needs to come after the touchend listener because the tap
22489
+ // listener cancels out any reportedUserActivity when setting userActive(false)
22490
+ this.on(this.tech_, 'tap', this.handleTechTap_);
22491
+ };
22492
+
22493
+ /**
22494
+ * Remove the listeners used for click and tap controls. This is needed for
22495
+ * toggling to controls disabled, where a tap/touch should do nothing.
22496
+ *
22497
+ * @private
22498
+ */
22499
+
22500
+
22501
+ Player.prototype.removeTechControlsListeners_ = function removeTechControlsListeners_() {
22502
+ // We don't want to just use `this.off()` because there might be other needed
22503
+ // listeners added by techs that extend this.
22504
+ this.off(this.tech_, 'tap', this.handleTechTap_);
22505
+ this.off(this.tech_, 'touchstart', this.handleTechTouchStart_);
22506
+ this.off(this.tech_, 'touchmove', this.handleTechTouchMove_);
22507
+ this.off(this.tech_, 'touchend', this.handleTechTouchEnd_);
22508
+ this.off(this.tech_, 'mousedown', this.handleTechClick_);
22509
+ this.off(this.tech_, 'dblclick', this.handleTechDoubleClick_);
22510
+ };
22511
+
22512
+ /**
22513
+ * Player waits for the tech to be ready
22514
+ *
22515
+ * @private
22516
+ */
22517
+
22518
+
22519
+ Player.prototype.handleTechReady_ = function handleTechReady_() {
22520
+ this.triggerReady();
22521
+
22522
+ // Keep the same volume as before
22523
+ if (this.cache_.volume) {
22524
+ this.techCall_('setVolume', this.cache_.volume);
22525
+ }
22526
+
22527
+ // Look if the tech found a higher resolution poster while loading
22528
+ this.handleTechPosterChange_();
22529
+
22530
+ // Update the duration if available
22531
+ this.handleTechDurationChange_();
22532
+ };
22533
+
22534
+ /**
22535
+ * Retrigger the `loadstart` event that was triggered by the {@link Tech}. This
22536
+ * function will also trigger {@link Player#firstplay} if it is the first loadstart
22537
+ * for a video.
22538
+ *
22539
+ * @fires Player#loadstart
22540
+ * @fires Player#firstplay
22541
+ * @listens Tech#loadstart
22542
+ * @private
22543
+ */
22544
+
22545
+
22546
+ Player.prototype.handleTechLoadStart_ = function handleTechLoadStart_() {
22547
+ // TODO: Update to use `emptied` event instead. See #1277.
22548
+
22549
+ this.removeClass('vjs-ended');
22550
+ this.removeClass('vjs-seeking');
22551
+
22552
+ // reset the error state
22553
+ this.error(null);
22554
+
22555
+ // If it's already playing we want to trigger a firstplay event now.
22556
+ // The firstplay event relies on both the play and loadstart events
22557
+ // which can happen in any order for a new source
22558
+ if (!this.paused()) {
22559
+ /**
22560
+ * Fired when the user agent begins looking for media data
22561
+ *
22562
+ * @event Player#loadstart
22563
+ * @type {EventTarget~Event}
22564
+ */
22565
+ this.trigger('loadstart');
22566
+ this.trigger('firstplay');
22567
+ } else {
22568
+ // reset the hasStarted state
22569
+ this.hasStarted(false);
22570
+ this.trigger('loadstart');
22571
+ }
22572
+
22573
+ // autoplay happens after loadstart for the browser,
22574
+ // so we mimic that behavior
22575
+ this.manualAutoplay_(this.autoplay());
22576
+ };
22577
+
22578
+ /**
22579
+ * Handle autoplay string values, rather than the typical boolean
22580
+ * values that should be handled by the tech. Note that this is not
22581
+ * part of any specification. Valid values and what they do can be
22582
+ * found on the autoplay getter at Player#autoplay()
22583
+ */
22584
+
22585
+
22586
+ Player.prototype.manualAutoplay_ = function manualAutoplay_(type) {
22587
+ var _this4 = this;
22588
+
22589
+ if (!this.tech_ || typeof type !== 'string') {
22590
+ return;
22591
+ }
22592
+
22593
+ var muted = function muted() {
22594
+ var previouslyMuted = _this4.muted();
22595
+
22596
+ _this4.muted(true);
22597
+
22598
+ var playPromise = _this4.play();
22599
+
22600
+ if (!playPromise || !playPromise.then || !playPromise.catch) {
22601
+ return;
22602
+ }
22603
+
22604
+ return playPromise.catch(function (e) {
22605
+ // restore old value of muted on failure
22606
+ _this4.muted(previouslyMuted);
22607
+ });
22608
+ };
22609
+
22610
+ var promise = void 0;
22611
+
22612
+ if (type === 'any') {
22613
+ promise = this.play();
22614
+
22615
+ if (promise && promise.then && promise.catch) {
22616
+ promise.catch(function () {
22617
+ return muted();
22618
+ });
22619
+ }
22620
+ } else if (type === 'muted') {
22621
+ promise = muted();
22622
+ } else {
22623
+ promise = this.play();
22624
+ }
22625
+
22626
+ if (!promise || !promise.then || !promise.catch) {
22627
+ return;
22628
+ }
22629
+
22630
+ return promise.then(function () {
22631
+ _this4.trigger({ type: 'autoplay-success', autoplay: type });
22632
+ }).catch(function (e) {
22633
+ _this4.trigger({ type: 'autoplay-failure', autoplay: type });
22634
+ });
22635
+ };
22636
+
22637
+ /**
22638
+ * Update the internal source caches so that we return the correct source from
22639
+ * `src()`, `currentSource()`, and `currentSources()`.
22640
+ *
22641
+ * > Note: `currentSources` will not be updated if the source that is passed in exists
22642
+ * in the current `currentSources` cache.
22643
+ *
22644
+ *
22645
+ * @param {Tech~SourceObject} srcObj
22646
+ * A string or object source to update our caches to.
22647
+ */
22648
+
22649
+
22650
+ Player.prototype.updateSourceCaches_ = function updateSourceCaches_() {
22651
+ var srcObj = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
22652
+
22653
+
22654
+ var src = srcObj;
22655
+ var type = '';
22656
+
22657
+ if (typeof src !== 'string') {
22658
+ src = srcObj.src;
22659
+ type = srcObj.type;
22660
+ }
22661
+ // make sure all the caches are set to default values
22662
+ // to prevent null checking
22663
+ this.cache_.source = this.cache_.source || {};
22664
+ this.cache_.sources = this.cache_.sources || [];
22665
+
22666
+ // try to get the type of the src that was passed in
22667
+ if (src && !type) {
22668
+ type = findMimetype(this, src);
22669
+ }
22670
+
22671
+ // update `currentSource` cache always
22672
+ this.cache_.source = mergeOptions({}, srcObj, { src: src, type: type });
22673
+
22674
+ var matchingSources = this.cache_.sources.filter(function (s) {
22675
+ return s.src && s.src === src;
22676
+ });
22677
+ var sourceElSources = [];
22678
+ var sourceEls = this.$$('source');
22679
+ var matchingSourceEls = [];
22680
+
22681
+ for (var i = 0; i < sourceEls.length; i++) {
22682
+ var sourceObj = getAttributes(sourceEls[i]);
22683
+
22684
+ sourceElSources.push(sourceObj);
22685
+
22686
+ if (sourceObj.src && sourceObj.src === src) {
22687
+ matchingSourceEls.push(sourceObj.src);
22688
+ }
22689
+ }
22690
+
22691
+ // if we have matching source els but not matching sources
22692
+ // the current source cache is not up to date
22693
+ if (matchingSourceEls.length && !matchingSources.length) {
22694
+ this.cache_.sources = sourceElSources;
22695
+ // if we don't have matching source or source els set the
22696
+ // sources cache to the `currentSource` cache
22697
+ } else if (!matchingSources.length) {
22698
+ this.cache_.sources = [this.cache_.source];
22699
+ }
22700
+
22701
+ // update the tech `src` cache
22702
+ this.cache_.src = src;
22703
+ };
22704
+
22705
+ /**
22706
+ * *EXPERIMENTAL* Fired when the source is set or changed on the {@link Tech}
22707
+ * causing the media element to reload.
22708
+ *
22709
+ * It will fire for the initial source and each subsequent source.
22710
+ * This event is a custom event from Video.js and is triggered by the {@link Tech}.
22711
+ *
22712
+ * The event object for this event contains a `src` property that will contain the source
22713
+ * that was available when the event was triggered. This is generally only necessary if Video.js
22714
+ * is switching techs while the source was being changed.
22715
+ *
22716
+ * It is also fired when `load` is called on the player (or media element)
22717
+ * because the {@link https://html.spec.whatwg.org/multipage/media.html#dom-media-load|specification for `load`}
22718
+ * says that the resource selection algorithm needs to be aborted and restarted.
22719
+ * In this case, it is very likely that the `src` property will be set to the
22720
+ * empty string `""` to indicate we do not know what the source will be but
22721
+ * that it is changing.
22722
+ *
22723
+ * *This event is currently still experimental and may change in minor releases.*
22724
+ * __To use this, pass `enableSourceset` option to the player.__
22725
+ *
22726
+ * @event Player#sourceset
22727
+ * @type {EventTarget~Event}
22728
+ * @prop {string} src
22729
+ * The source url available when the `sourceset` was triggered.
22730
+ * It will be an empty string if we cannot know what the source is
22731
+ * but know that the source will change.
22732
+ */
22733
+ /**
22734
+ * Retrigger the `sourceset` event that was triggered by the {@link Tech}.
22735
+ *
22736
+ * @fires Player#sourceset
22737
+ * @listens Tech#sourceset
22738
+ * @private
22739
+ */
22740
+
22741
+
22742
+ Player.prototype.handleTechSourceset_ = function handleTechSourceset_(event) {
22743
+ var _this5 = this;
22744
+
22745
+ // only update the source cache when the source
22746
+ // was not updated using the player api
22747
+ if (!this.changingSrc_) {
22748
+ // update the source to the intial source right away
22749
+ // in some cases this will be empty string
22750
+ this.updateSourceCaches_(event.src);
22751
+
22752
+ // if the `sourceset` `src` was an empty string
22753
+ // wait for a `loadstart` to update the cache to `currentSrc`.
22754
+ // If a sourceset happens before a `loadstart`, we reset the state
22755
+ // as this function will be called again.
22756
+ if (!event.src) {
22757
+ var updateCache = function updateCache(e) {
22758
+ if (e.type !== 'sourceset') {
22759
+ _this5.updateSourceCaches_(_this5.techGet_('currentSrc'));
22760
+ }
22761
+
22762
+ _this5.tech_.off(['sourceset', 'loadstart'], updateCache);
22763
+ };
22764
+
22765
+ this.tech_.one(['sourceset', 'loadstart'], updateCache);
22766
+ }
22767
+ }
22768
+
22769
+ this.trigger({
22770
+ src: event.src,
22771
+ type: 'sourceset'
22772
+ });
22773
+ };
22774
+
22775
+ /**
22776
+ * Add/remove the vjs-has-started class
22777
+ *
22778
+ * @fires Player#firstplay
22779
+ *
22780
+ * @param {boolean} request
22781
+ * - true: adds the class
22782
+ * - false: remove the class
22783
+ *
22784
+ * @return {boolean}
22785
+ * the boolean value of hasStarted_
22786
+ */
22787
+
22788
+
22789
+ Player.prototype.hasStarted = function hasStarted(request) {
22790
+ if (request === undefined) {
22791
+ // act as getter, if we have no request to change
22792
+ return this.hasStarted_;
22793
+ }
22794
+
22795
+ if (request === this.hasStarted_) {
22796
+ return;
22797
+ }
22798
+
22799
+ this.hasStarted_ = request;
22800
+
22801
+ if (this.hasStarted_) {
22802
+ this.addClass('vjs-has-started');
22803
+ this.trigger('firstplay');
22804
+ } else {
22805
+ this.removeClass('vjs-has-started');
22806
+ }
22807
+ };
22808
+
22809
+ /**
22810
+ * Fired whenever the media begins or resumes playback
22811
+ *
22812
+ * @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#dom-media-play}
22813
+ * @fires Player#play
22814
+ * @listens Tech#play
22815
+ * @private
22816
+ */
22817
+
22818
+
22819
+ Player.prototype.handleTechPlay_ = function handleTechPlay_() {
22820
+ this.removeClass('vjs-ended');
22821
+ this.removeClass('vjs-paused');
22822
+ this.addClass('vjs-playing');
22823
+
22824
+ // hide the poster when the user hits play
22825
+ this.hasStarted(true);
22826
+ /**
22827
+ * Triggered whenever an {@link Tech#play} event happens. Indicates that
22828
+ * playback has started or resumed.
22829
+ *
22830
+ * @event Player#play
22831
+ * @type {EventTarget~Event}
22832
+ */
22833
+ this.trigger('play');
22834
+ };
22835
+
22836
+ /**
22837
+ * Retrigger the `ratechange` event that was triggered by the {@link Tech}.
22838
+ *
22839
+ * If there were any events queued while the playback rate was zero, fire
22840
+ * those events now.
22841
+ *
22842
+ * @private
22843
+ * @method Player#handleTechRateChange_
22844
+ * @fires Player#ratechange
22845
+ * @listens Tech#ratechange
22846
+ */
22847
+
22848
+
22849
+ Player.prototype.handleTechRateChange_ = function handleTechRateChange_() {
22850
+ if (this.tech_.playbackRate() > 0 && this.cache_.lastPlaybackRate === 0) {
22851
+ this.queuedCallbacks_.forEach(function (queued) {
22852
+ return queued.callback(queued.event);
22853
+ });
22854
+ this.queuedCallbacks_ = [];
22855
+ }
22856
+ this.cache_.lastPlaybackRate = this.tech_.playbackRate();
22857
+ /**
22858
+ * Fires when the playing speed of the audio/video is changed
22859
+ *
22860
+ * @event Player#ratechange
22861
+ * @type {event}
22862
+ */
22863
+ this.trigger('ratechange');
22864
+ };
22865
+
22866
+ /**
22867
+ * Retrigger the `waiting` event that was triggered by the {@link Tech}.
22868
+ *
22869
+ * @fires Player#waiting
22870
+ * @listens Tech#waiting
22871
+ * @private
22872
+ */
22873
+
22874
+
22875
+ Player.prototype.handleTechWaiting_ = function handleTechWaiting_() {
22876
+ var _this6 = this;
22877
+
22878
+ this.addClass('vjs-waiting');
22879
+ /**
22880
+ * A readyState change on the DOM element has caused playback to stop.
22881
+ *
22882
+ * @event Player#waiting
22883
+ * @type {EventTarget~Event}
22884
+ */
22885
+ this.trigger('waiting');
22886
+ this.one('timeupdate', function () {
22887
+ return _this6.removeClass('vjs-waiting');
22888
+ });
22889
+ };
22890
+
22891
+ /**
22892
+ * Retrigger the `canplay` event that was triggered by the {@link Tech}.
22893
+ * > Note: This is not consistent between browsers. See #1351
22894
+ *
22895
+ * @fires Player#canplay
22896
+ * @listens Tech#canplay
22897
+ * @private
22898
+ */
22899
+
22900
+
22901
+ Player.prototype.handleTechCanPlay_ = function handleTechCanPlay_() {
22902
+ this.removeClass('vjs-waiting');
22903
+ /**
22904
+ * The media has a readyState of HAVE_FUTURE_DATA or greater.
22905
+ *
22906
+ * @event Player#canplay
22907
+ * @type {EventTarget~Event}
22908
+ */
22909
+ this.trigger('canplay');
22910
+ };
22911
+
22912
+ /**
22913
+ * Retrigger the `canplaythrough` event that was triggered by the {@link Tech}.
22914
+ *
22915
+ * @fires Player#canplaythrough
22916
+ * @listens Tech#canplaythrough
22917
+ * @private
22918
+ */
22919
+
22920
+
22921
+ Player.prototype.handleTechCanPlayThrough_ = function handleTechCanPlayThrough_() {
22922
+ this.removeClass('vjs-waiting');
22923
+ /**
22924
+ * The media has a readyState of HAVE_ENOUGH_DATA or greater. This means that the
22925
+ * entire media file can be played without buffering.
22926
+ *
22927
+ * @event Player#canplaythrough
22928
+ * @type {EventTarget~Event}
22929
+ */
22930
+ this.trigger('canplaythrough');
22931
+ };
22932
+
22933
+ /**
22934
+ * Retrigger the `playing` event that was triggered by the {@link Tech}.
22935
+ *
22936
+ * @fires Player#playing
22937
+ * @listens Tech#playing
22938
+ * @private
22939
+ */
22940
+
22941
+
22942
+ Player.prototype.handleTechPlaying_ = function handleTechPlaying_() {
22943
+ this.removeClass('vjs-waiting');
22944
+ /**
22945
+ * The media is no longer blocked from playback, and has started playing.
22946
+ *
22947
+ * @event Player#playing
22948
+ * @type {EventTarget~Event}
22949
+ */
22950
+ this.trigger('playing');
22951
+ };
22952
+
22953
+ /**
22954
+ * Retrigger the `seeking` event that was triggered by the {@link Tech}.
22955
+ *
22956
+ * @fires Player#seeking
22957
+ * @listens Tech#seeking
22958
+ * @private
22959
+ */
22960
+
22961
+
22962
+ Player.prototype.handleTechSeeking_ = function handleTechSeeking_() {
22963
+ this.addClass('vjs-seeking');
22964
+ /**
22965
+ * Fired whenever the player is jumping to a new time
22966
+ *
22967
+ * @event Player#seeking
22968
+ * @type {EventTarget~Event}
22969
+ */
22970
+ this.trigger('seeking');
22971
+ };
22972
+
22973
+ /**
22974
+ * Retrigger the `seeked` event that was triggered by the {@link Tech}.
22975
+ *
22976
+ * @fires Player#seeked
22977
+ * @listens Tech#seeked
22978
+ * @private
22979
+ */
22980
+
22981
+
22982
+ Player.prototype.handleTechSeeked_ = function handleTechSeeked_() {
22983
+ this.removeClass('vjs-seeking');
22984
+ /**
22985
+ * Fired when the player has finished jumping to a new time
22986
+ *
22987
+ * @event Player#seeked
22988
+ * @type {EventTarget~Event}
22989
+ */
22990
+ this.trigger('seeked');
22991
+ };
22992
+
22993
+ /**
22994
+ * Retrigger the `firstplay` event that was triggered by the {@link Tech}.
22995
+ *
22996
+ * @fires Player#firstplay
22997
+ * @listens Tech#firstplay
22998
+ * @deprecated As of 6.0 firstplay event is deprecated.
22999
+ * @deprecated As of 6.0 passing the `starttime` option to the player and the firstplay event are deprecated.
23000
+ * @private
23001
+ */
23002
+
23003
+
23004
+ Player.prototype.handleTechFirstPlay_ = function handleTechFirstPlay_() {
23005
+ // If the first starttime attribute is specified
23006
+ // then we will start at the given offset in seconds
23007
+ if (this.options_.starttime) {
23008
+ log$1.warn('Passing the `starttime` option to the player will be deprecated in 6.0');
23009
+ this.currentTime(this.options_.starttime);
23010
+ }
23011
+
23012
+ this.addClass('vjs-has-started');
23013
+ /**
23014
+ * Fired the first time a video is played. Not part of the HLS spec, and this is
23015
+ * probably not the best implementation yet, so use sparingly. If you don't have a
23016
+ * reason to prevent playback, use `myPlayer.one('play');` instead.
23017
+ *
23018
+ * @event Player#firstplay
23019
+ * @deprecated As of 6.0 firstplay event is deprecated.
23020
+ * @type {EventTarget~Event}
23021
+ */
23022
+ this.trigger('firstplay');
23023
+ };
23024
+
23025
+ /**
23026
+ * Retrigger the `pause` event that was triggered by the {@link Tech}.
23027
+ *
23028
+ * @fires Player#pause
23029
+ * @listens Tech#pause
23030
+ * @private
23031
+ */
23032
+
23033
+
23034
+ Player.prototype.handleTechPause_ = function handleTechPause_() {
23035
+ this.removeClass('vjs-playing');
23036
+ this.addClass('vjs-paused');
23037
+ /**
23038
+ * Fired whenever the media has been paused
23039
+ *
23040
+ * @event Player#pause
23041
+ * @type {EventTarget~Event}
23042
+ */
23043
+ this.trigger('pause');
23044
+ };
23045
+
23046
+ /**
23047
+ * Retrigger the `ended` event that was triggered by the {@link Tech}.
23048
+ *
23049
+ * @fires Player#ended
23050
+ * @listens Tech#ended
23051
+ * @private
23052
+ */
23053
+
23054
+
23055
+ Player.prototype.handleTechEnded_ = function handleTechEnded_() {
23056
+ this.addClass('vjs-ended');
23057
+ if (this.options_.loop) {
23058
+ this.currentTime(0);
23059
+ this.play();
23060
+ } else if (!this.paused()) {
23061
+ this.pause();
23062
+ }
23063
+
23064
+ /**
23065
+ * Fired when the end of the media resource is reached (currentTime == duration)
23066
+ *
23067
+ * @event Player#ended
23068
+ * @type {EventTarget~Event}
23069
+ */
23070
+ this.trigger('ended');
23071
+ };
23072
+
23073
+ /**
23074
+ * Fired when the duration of the media resource is first known or changed
23075
+ *
23076
+ * @listens Tech#durationchange
23077
+ * @private
23078
+ */
23079
+
23080
+
23081
+ Player.prototype.handleTechDurationChange_ = function handleTechDurationChange_() {
23082
+ this.duration(this.techGet_('duration'));
23083
+ };
23084
+
23085
+ /**
23086
+ * Handle a click on the media element to play/pause
23087
+ *
23088
+ * @param {EventTarget~Event} event
23089
+ * the event that caused this function to trigger
23090
+ *
23091
+ * @listens Tech#mousedown
23092
+ * @private
23093
+ */
23094
+
23095
+
23096
+ Player.prototype.handleTechClick_ = function handleTechClick_(event) {
23097
+ if (!isSingleLeftClick(event)) {
23098
+ return;
23099
+ }
23100
+
23101
+ // When controls are disabled a click should not toggle playback because
23102
+ // the click is considered a control
23103
+ if (!this.controls_) {
23104
+ return;
23105
+ }
23106
+
23107
+ if (this.paused()) {
23108
+ silencePromise(this.play());
23109
+ } else {
23110
+ this.pause();
23111
+ }
23112
+ };
23113
+
23114
+ /**
23115
+ * Handle a double-click on the media element to enter/exit fullscreen
23116
+ *
23117
+ * @param {EventTarget~Event} event
23118
+ * the event that caused this function to trigger
23119
+ *
23120
+ * @listens Tech#dblclick
23121
+ * @private
23122
+ */
23123
+
23124
+
23125
+ Player.prototype.handleTechDoubleClick_ = function handleTechDoubleClick_(event) {
23126
+ if (!this.controls_) {
23127
+ return;
23128
+ }
23129
+
23130
+ // we do not want to toggle fullscreen state
23131
+ // when double-clicking inside a control bar or a modal
23132
+ var inAllowedEls = Array.prototype.some.call(this.$$('.vjs-control-bar, .vjs-modal-dialog'), function (el) {
23133
+ return el.contains(event.target);
23134
+ });
23135
+
23136
+ if (!inAllowedEls) {
23137
+ if (this.isFullscreen()) {
23138
+ this.exitFullscreen();
23139
+ } else {
23140
+ this.requestFullscreen();
23141
+ }
23142
+ }
23143
+ };
23144
+
23145
+ /**
23146
+ * Handle a tap on the media element. It will toggle the user
23147
+ * activity state, which hides and shows the controls.
23148
+ *
23149
+ * @listens Tech#tap
23150
+ * @private
23151
+ */
23152
+
23153
+
23154
+ Player.prototype.handleTechTap_ = function handleTechTap_() {
23155
+ this.userActive(!this.userActive());
23156
+ };
23157
+
23158
+ /**
23159
+ * Handle touch to start
23160
+ *
23161
+ * @listens Tech#touchstart
23162
+ * @private
23163
+ */
23164
+
23165
+
23166
+ Player.prototype.handleTechTouchStart_ = function handleTechTouchStart_() {
23167
+ this.userWasActive = this.userActive();
23168
+ };
23169
+
23170
+ /**
23171
+ * Handle touch to move
23172
+ *
23173
+ * @listens Tech#touchmove
23174
+ * @private
23175
+ */
23176
+
23177
+
23178
+ Player.prototype.handleTechTouchMove_ = function handleTechTouchMove_() {
23179
+ if (this.userWasActive) {
23180
+ this.reportUserActivity();
23181
+ }
23182
+ };
23183
+
23184
+ /**
23185
+ * Handle touch to end
23186
+ *
23187
+ * @param {EventTarget~Event} event
23188
+ * the touchend event that triggered
23189
+ * this function
23190
+ *
23191
+ * @listens Tech#touchend
23192
+ * @private
23193
+ */
23194
+
23195
+
23196
+ Player.prototype.handleTechTouchEnd_ = function handleTechTouchEnd_(event) {
23197
+ // Stop the mouse events from also happening
23198
+ event.preventDefault();
23199
+ };
23200
+
23201
+ /**
23202
+ * Fired when the player switches in or out of fullscreen mode
23203
+ *
23204
+ * @private
23205
+ * @listens Player#fullscreenchange
23206
+ */
23207
+
23208
+
23209
+ Player.prototype.handleFullscreenChange_ = function handleFullscreenChange_() {
23210
+ if (this.isFullscreen()) {
23211
+ this.addClass('vjs-fullscreen');
23212
+ } else {
23213
+ this.removeClass('vjs-fullscreen');
23214
+ }
23215
+ };
23216
+
23217
+ /**
23218
+ * native click events on the SWF aren't triggered on IE11, Win8.1RT
23219
+ * use stageclick events triggered from inside the SWF instead
23220
+ *
23221
+ * @private
23222
+ * @listens stageclick
23223
+ */
23224
+
23225
+
23226
+ Player.prototype.handleStageClick_ = function handleStageClick_() {
23227
+ this.reportUserActivity();
23228
+ };
23229
+
23230
+ /**
23231
+ * Handle Tech Fullscreen Change
23232
+ *
23233
+ * @param {EventTarget~Event} event
23234
+ * the fullscreenchange event that triggered this function
23235
+ *
23236
+ * @param {Object} data
23237
+ * the data that was sent with the event
23238
+ *
23239
+ * @private
23240
+ * @listens Tech#fullscreenchange
23241
+ * @fires Player#fullscreenchange
23242
+ */
23243
+
23244
+
23245
+ Player.prototype.handleTechFullscreenChange_ = function handleTechFullscreenChange_(event, data) {
23246
+ if (data) {
23247
+ this.isFullscreen(data.isFullscreen);
23248
+ }
23249
+ /**
23250
+ * Fired when going in and out of fullscreen.
23251
+ *
23252
+ * @event Player#fullscreenchange
23253
+ * @type {EventTarget~Event}
23254
+ */
23255
+ this.trigger('fullscreenchange');
23256
+ };
23257
+
23258
+ /**
23259
+ * Fires when an error occurred during the loading of an audio/video.
23260
+ *
23261
+ * @private
23262
+ * @listens Tech#error
23263
+ */
23264
+
23265
+
23266
+ Player.prototype.handleTechError_ = function handleTechError_() {
23267
+ var error = this.tech_.error();
23268
+
23269
+ this.error(error);
23270
+ };
23271
+
23272
+ /**
23273
+ * Retrigger the `textdata` event that was triggered by the {@link Tech}.
23274
+ *
23275
+ * @fires Player#textdata
23276
+ * @listens Tech#textdata
23277
+ * @private
23278
+ */
23279
+
23280
+
23281
+ Player.prototype.handleTechTextData_ = function handleTechTextData_() {
23282
+ var data = null;
23283
+
23284
+ if (arguments.length > 1) {
23285
+ data = arguments[1];
23286
+ }
23287
+
23288
+ /**
23289
+ * Fires when we get a textdata event from tech
23290
+ *
23291
+ * @event Player#textdata
23292
+ * @type {EventTarget~Event}
23293
+ */
23294
+ this.trigger('textdata', data);
23295
+ };
23296
+
23297
+ /**
23298
+ * Get object for cached values.
23299
+ *
23300
+ * @return {Object}
23301
+ * get the current object cache
23302
+ */
23303
+
23304
+
23305
+ Player.prototype.getCache = function getCache() {
23306
+ return this.cache_;
23307
+ };
23308
+
23309
+ /**
23310
+ * Pass values to the playback tech
23311
+ *
23312
+ * @param {string} [method]
23313
+ * the method to call
23314
+ *
23315
+ * @param {Object} arg
23316
+ * the argument to pass
23317
+ *
23318
+ * @private
23319
+ */
23320
+
23321
+
23322
+ Player.prototype.techCall_ = function techCall_(method, arg) {
23323
+ // If it's not ready yet, call method when it is
23324
+
23325
+ this.ready(function () {
23326
+ if (method in allowedSetters) {
23327
+ return set$1(this.middleware_, this.tech_, method, arg);
23328
+ } else if (method in allowedMediators) {
23329
+ return mediate(this.middleware_, this.tech_, method, arg);
23330
+ }
23331
+
23332
+ try {
23333
+ if (this.tech_) {
23334
+ this.tech_[method](arg);
23335
+ }
23336
+ } catch (e) {
23337
+ log$1(e);
23338
+ throw e;
23339
+ }
23340
+ }, true);
23341
+ };
23342
+
23343
+ /**
23344
+ * Get calls can't wait for the tech, and sometimes don't need to.
23345
+ *
23346
+ * @param {string} method
23347
+ * Tech method
23348
+ *
23349
+ * @return {Function|undefined}
23350
+ * the method or undefined
23351
+ *
23352
+ * @private
23353
+ */
23354
+
23355
+
23356
+ Player.prototype.techGet_ = function techGet_(method) {
23357
+ if (!this.tech_ || !this.tech_.isReady_) {
23358
+ return;
23359
+ }
23360
+
23361
+ if (method in allowedGetters) {
23362
+ return get$1(this.middleware_, this.tech_, method);
23363
+ } else if (method in allowedMediators) {
23364
+ return mediate(this.middleware_, this.tech_, method);
23365
+ }
23366
+
23367
+ // Flash likes to die and reload when you hide or reposition it.
23368
+ // In these cases the object methods go away and we get errors.
23369
+ // When that happens we'll catch the errors and inform tech that it's not ready any more.
23370
+ try {
23371
+ return this.tech_[method]();
23372
+ } catch (e) {
23373
+
23374
+ // When building additional tech libs, an expected method may not be defined yet
23375
+ if (this.tech_[method] === undefined) {
23376
+ log$1('Video.js: ' + method + ' method not defined for ' + this.techName_ + ' playback technology.', e);
23377
+ throw e;
23378
+ }
23379
+
23380
+ // When a method isn't available on the object it throws a TypeError
23381
+ if (e.name === 'TypeError') {
23382
+ log$1('Video.js: ' + method + ' unavailable on ' + this.techName_ + ' playback technology element.', e);
23383
+ this.tech_.isReady_ = false;
23384
+ throw e;
23385
+ }
23386
+
23387
+ // If error unknown, just log and throw
23388
+ log$1(e);
23389
+ throw e;
23390
+ }
23391
+ };
23392
+
23393
+ /**
23394
+ * Attempt to begin playback at the first opportunity.
23395
+ *
23396
+ * @return {Promise|undefined}
23397
+ * Returns a `Promise` only if the browser returns one and the player
23398
+ * is ready to begin playback. For some browsers and all non-ready
23399
+ * situations, this will return `undefined`.
23400
+ */
23401
+
23402
+
23403
+ Player.prototype.play = function play() {
23404
+ var _this7 = this;
23405
+
23406
+ // If this is called while we have a play queued up on a loadstart, remove
23407
+ // that listener to avoid getting in a potentially bad state.
23408
+ if (this.playOnLoadstart_) {
23409
+ this.off('loadstart', this.playOnLoadstart_);
23410
+ }
23411
+
23412
+ // If the player/tech is not ready, queue up another call to `play()` for
23413
+ // when it is. This will loop back into this method for another attempt at
23414
+ // playback when the tech is ready.
23415
+ if (!this.isReady_) {
23416
+
23417
+ // Bail out if we're already waiting for `ready`!
23418
+ if (this.playWaitingForReady_) {
23419
+ return;
23420
+ }
23421
+
23422
+ this.playWaitingForReady_ = true;
23423
+ this.ready(function () {
23424
+ _this7.playWaitingForReady_ = false;
23425
+ silencePromise(_this7.play());
23426
+ });
23427
+
23428
+ // If the player/tech is ready and we have a source, we can attempt playback.
23429
+ } else if (!this.changingSrc_ && (this.src() || this.currentSrc())) {
23430
+ return this.techGet_('play');
23431
+
23432
+ // If the tech is ready, but we do not have a source, we'll need to wait
23433
+ // for both the `ready` and a `loadstart` when the source is finally
23434
+ // resolved by middleware and set on the player.
23435
+ //
23436
+ // This can happen if `play()` is called while changing sources or before
23437
+ // one has been set on the player.
23438
+ } else {
23439
+
23440
+ this.playOnLoadstart_ = function () {
23441
+ _this7.playOnLoadstart_ = null;
23442
+ silencePromise(_this7.play());
23443
+ };
23444
+
23445
+ this.one('loadstart', this.playOnLoadstart_);
23446
+ }
23447
+ };
23448
+
23449
+ /**
23450
+ * Pause the video playback
23451
+ *
23452
+ * @return {Player}
23453
+ * A reference to the player object this function was called on
23454
+ */
23455
+
23456
+
23457
+ Player.prototype.pause = function pause() {
23458
+ this.techCall_('pause');
23459
+ };
23460
+
23461
+ /**
23462
+ * Check if the player is paused or has yet to play
23463
+ *
23464
+ * @return {boolean}
23465
+ * - false: if the media is currently playing
23466
+ * - true: if media is not currently playing
23467
+ */
23468
+
23469
+
23470
+ Player.prototype.paused = function paused() {
23471
+ // The initial state of paused should be true (in Safari it's actually false)
23472
+ return this.techGet_('paused') === false ? false : true;
23473
+ };
23474
+
23475
+ /**
23476
+ * Get a TimeRange object representing the current ranges of time that the user
23477
+ * has played.
23478
+ *
23479
+ * @return {TimeRange}
23480
+ * A time range object that represents all the increments of time that have
23481
+ * been played.
23482
+ */
23483
+
23484
+
23485
+ Player.prototype.played = function played() {
23486
+ return this.techGet_('played') || createTimeRanges(0, 0);
23487
+ };
23488
+
23489
+ /**
23490
+ * Returns whether or not the user is "scrubbing". Scrubbing is
23491
+ * when the user has clicked the progress bar handle and is
23492
+ * dragging it along the progress bar.
23493
+ *
23494
+ * @param {boolean} [isScrubbing]
23495
+ * whether the user is or is not scrubbing
23496
+ *
23497
+ * @return {boolean}
23498
+ * The value of scrubbing when getting
23499
+ */
23500
+
23501
+
23502
+ Player.prototype.scrubbing = function scrubbing(isScrubbing) {
23503
+ if (typeof isScrubbing === 'undefined') {
23504
+ return this.scrubbing_;
23505
+ }
23506
+ this.scrubbing_ = !!isScrubbing;
23507
+
23508
+ if (isScrubbing) {
23509
+ this.addClass('vjs-scrubbing');
23510
+ } else {
23511
+ this.removeClass('vjs-scrubbing');
23512
+ }
23513
+ };
23514
+
23515
+ /**
23516
+ * Get or set the current time (in seconds)
23517
+ *
23518
+ * @param {number|string} [seconds]
23519
+ * The time to seek to in seconds
23520
+ *
23521
+ * @return {number}
23522
+ * - the current time in seconds when getting
23523
+ */
23524
+
23525
+
23526
+ Player.prototype.currentTime = function currentTime(seconds) {
23527
+ if (typeof seconds !== 'undefined') {
23528
+ if (seconds < 0) {
23529
+ seconds = 0;
23530
+ }
23531
+ this.techCall_('setCurrentTime', seconds);
23532
+ return;
23533
+ }
23534
+
23535
+ // cache last currentTime and return. default to 0 seconds
23536
+ //
23537
+ // Caching the currentTime is meant to prevent a massive amount of reads on the tech's
23538
+ // currentTime when scrubbing, but may not provide much performance benefit afterall.
23539
+ // Should be tested. Also something has to read the actual current time or the cache will
23540
+ // never get updated.
23541
+ this.cache_.currentTime = this.techGet_('currentTime') || 0;
23542
+ return this.cache_.currentTime;
23543
+ };
23544
+
23545
+ /**
23546
+ * Normally gets the length in time of the video in seconds;
23547
+ * in all but the rarest use cases an argument will NOT be passed to the method
23548
+ *
23549
+ * > **NOTE**: The video must have started loading before the duration can be
23550
+ * known, and in the case of Flash, may not be known until the video starts
23551
+ * playing.
23552
+ *
23553
+ * @fires Player#durationchange
23554
+ *
23555
+ * @param {number} [seconds]
23556
+ * The duration of the video to set in seconds
23557
+ *
23558
+ * @return {number}
23559
+ * - The duration of the video in seconds when getting
23560
+ */
23561
+
23562
+
23563
+ Player.prototype.duration = function duration(seconds) {
23564
+ if (seconds === undefined) {
23565
+ // return NaN if the duration is not known
23566
+ return this.cache_.duration !== undefined ? this.cache_.duration : NaN;
23567
+ }
23568
+
23569
+ seconds = parseFloat(seconds);
23570
+
23571
+ // Standardize on Infinity for signaling video is live
23572
+ if (seconds < 0) {
23573
+ seconds = Infinity;
23574
+ }
23575
+
23576
+ if (seconds !== this.cache_.duration) {
23577
+ // Cache the last set value for optimized scrubbing (esp. Flash)
23578
+ this.cache_.duration = seconds;
23579
+
23580
+ if (seconds === Infinity) {
23581
+ this.addClass('vjs-live');
23582
+ } else {
23583
+ this.removeClass('vjs-live');
23584
+ }
23585
+ /**
23586
+ * @event Player#durationchange
23587
+ * @type {EventTarget~Event}
23588
+ */
23589
+ this.trigger('durationchange');
23590
+ }
23591
+ };
23592
+
23593
+ /**
23594
+ * Calculates how much time is left in the video. Not part
23595
+ * of the native video API.
23596
+ *
23597
+ * @return {number}
23598
+ * The time remaining in seconds
23599
+ */
23600
+
23601
+
23602
+ Player.prototype.remainingTime = function remainingTime() {
23603
+ return this.duration() - this.currentTime();
23604
+ };
23605
+
23606
+ /**
23607
+ * A remaining time function that is intented to be used when
23608
+ * the time is to be displayed directly to the user.
23609
+ *
23610
+ * @return {number}
23611
+ * The rounded time remaining in seconds
23612
+ */
23613
+
23614
+
23615
+ Player.prototype.remainingTimeDisplay = function remainingTimeDisplay() {
23616
+ return Math.floor(this.duration()) - Math.floor(this.currentTime());
23617
+ };
23618
+
23619
+ //
23620
+ // Kind of like an array of portions of the video that have been downloaded.
23621
+
23622
+ /**
23623
+ * Get a TimeRange object with an array of the times of the video
23624
+ * that have been downloaded. If you just want the percent of the
23625
+ * video that's been downloaded, use bufferedPercent.
23626
+ *
23627
+ * @see [Buffered Spec]{@link http://dev.w3.org/html5/spec/video.html#dom-media-buffered}
23628
+ *
23629
+ * @return {TimeRange}
23630
+ * A mock TimeRange object (following HTML spec)
23631
+ */
23632
+
23633
+
23634
+ Player.prototype.buffered = function buffered() {
23635
+ var buffered = this.techGet_('buffered');
23636
+
23637
+ if (!buffered || !buffered.length) {
23638
+ buffered = createTimeRanges(0, 0);
23639
+ }
23640
+
23641
+ return buffered;
23642
+ };
23643
+
23644
+ /**
23645
+ * Get the percent (as a decimal) of the video that's been downloaded.
23646
+ * This method is not a part of the native HTML video API.
23647
+ *
23648
+ * @return {number}
23649
+ * A decimal between 0 and 1 representing the percent
23650
+ * that is buffered 0 being 0% and 1 being 100%
23651
+ */
23652
+
23653
+
23654
+ Player.prototype.bufferedPercent = function bufferedPercent$$1() {
23655
+ return bufferedPercent(this.buffered(), this.duration());
23656
+ };
23657
+
23658
+ /**
23659
+ * Get the ending time of the last buffered time range
23660
+ * This is used in the progress bar to encapsulate all time ranges.
23661
+ *
23662
+ * @return {number}
23663
+ * The end of the last buffered time range
23664
+ */
23665
+
23666
+
23667
+ Player.prototype.bufferedEnd = function bufferedEnd() {
23668
+ var buffered = this.buffered();
23669
+ var duration = this.duration();
23670
+ var end = buffered.end(buffered.length - 1);
23671
+
23672
+ if (end > duration) {
23673
+ end = duration;
23674
+ }
23675
+
23676
+ return end;
23677
+ };
23678
+
23679
+ /**
23680
+ * Get or set the current volume of the media
23681
+ *
23682
+ * @param {number} [percentAsDecimal]
23683
+ * The new volume as a decimal percent:
23684
+ * - 0 is muted/0%/off
23685
+ * - 1.0 is 100%/full
23686
+ * - 0.5 is half volume or 50%
23687
+ *
23688
+ * @return {number}
23689
+ * The current volume as a percent when getting
23690
+ */
23691
+
23692
+
23693
+ Player.prototype.volume = function volume(percentAsDecimal) {
23694
+ var vol = void 0;
23695
+
23696
+ if (percentAsDecimal !== undefined) {
23697
+ // Force value to between 0 and 1
23698
+ vol = Math.max(0, Math.min(1, parseFloat(percentAsDecimal)));
23699
+ this.cache_.volume = vol;
23700
+ this.techCall_('setVolume', vol);
23701
+
23702
+ if (vol > 0) {
23703
+ this.lastVolume_(vol);
23704
+ }
23705
+
23706
+ return;
23707
+ }
23708
+
23709
+ // Default to 1 when returning current volume.
23710
+ vol = parseFloat(this.techGet_('volume'));
23711
+ return isNaN(vol) ? 1 : vol;
23712
+ };
23713
+
23714
+ /**
23715
+ * Get the current muted state, or turn mute on or off
23716
+ *
23717
+ * @param {boolean} [muted]
23718
+ * - true to mute
23719
+ * - false to unmute
23720
+ *
23721
+ * @return {boolean}
23722
+ * - true if mute is on and getting
23723
+ * - false if mute is off and getting
23724
+ */
23725
+
23726
+
23727
+ Player.prototype.muted = function muted(_muted) {
23728
+ if (_muted !== undefined) {
23729
+ this.techCall_('setMuted', _muted);
23730
+ return;
23731
+ }
23732
+ return this.techGet_('muted') || false;
23733
+ };
23734
+
23735
+ /**
23736
+ * Get the current defaultMuted state, or turn defaultMuted on or off. defaultMuted
23737
+ * indicates the state of muted on initial playback.
23738
+ *
23739
+ * ```js
23740
+ * var myPlayer = videojs('some-player-id');
23741
+ *
23742
+ * myPlayer.src("http://www.example.com/path/to/video.mp4");
23743
+ *
23744
+ * // get, should be false
23745
+ * console.log(myPlayer.defaultMuted());
23746
+ * // set to true
23747
+ * myPlayer.defaultMuted(true);
23748
+ * // get should be true
23749
+ * console.log(myPlayer.defaultMuted());
23750
+ * ```
23751
+ *
23752
+ * @param {boolean} [defaultMuted]
23753
+ * - true to mute
23754
+ * - false to unmute
23755
+ *
23756
+ * @return {boolean|Player}
23757
+ * - true if defaultMuted is on and getting
23758
+ * - false if defaultMuted is off and getting
23759
+ * - A reference to the current player when setting
23760
+ */
23761
+
23762
+
23763
+ Player.prototype.defaultMuted = function defaultMuted(_defaultMuted) {
23764
+ if (_defaultMuted !== undefined) {
23765
+ return this.techCall_('setDefaultMuted', _defaultMuted);
23766
+ }
23767
+ return this.techGet_('defaultMuted') || false;
23768
+ };
23769
+
23770
+ /**
23771
+ * Get the last volume, or set it
23772
+ *
23773
+ * @param {number} [percentAsDecimal]
23774
+ * The new last volume as a decimal percent:
23775
+ * - 0 is muted/0%/off
23776
+ * - 1.0 is 100%/full
23777
+ * - 0.5 is half volume or 50%
23778
+ *
23779
+ * @return {number}
23780
+ * the current value of lastVolume as a percent when getting
23781
+ *
23782
+ * @private
23783
+ */
23784
+
23785
+
23786
+ Player.prototype.lastVolume_ = function lastVolume_(percentAsDecimal) {
23787
+ if (percentAsDecimal !== undefined && percentAsDecimal !== 0) {
23788
+ this.cache_.lastVolume = percentAsDecimal;
23789
+ return;
23790
+ }
23791
+ return this.cache_.lastVolume;
23792
+ };
23793
+
23794
+ /**
23795
+ * Check if current tech can support native fullscreen
23796
+ * (e.g. with built in controls like iOS, so not our flash swf)
23797
+ *
23798
+ * @return {boolean}
23799
+ * if native fullscreen is supported
23800
+ */
23801
+
23802
+
23803
+ Player.prototype.supportsFullScreen = function supportsFullScreen() {
23804
+ return this.techGet_('supportsFullScreen') || false;
23805
+ };
23806
+
23807
+ /**
23808
+ * Check if the player is in fullscreen mode or tell the player that it
23809
+ * is or is not in fullscreen mode.
23810
+ *
23811
+ * > NOTE: As of the latest HTML5 spec, isFullscreen is no longer an official
23812
+ * property and instead document.fullscreenElement is used. But isFullscreen is
23813
+ * still a valuable property for internal player workings.
23814
+ *
23815
+ * @param {boolean} [isFS]
23816
+ * Set the players current fullscreen state
23817
+ *
23818
+ * @return {boolean}
23819
+ * - true if fullscreen is on and getting
23820
+ * - false if fullscreen is off and getting
23821
+ */
23822
+
23823
+
23824
+ Player.prototype.isFullscreen = function isFullscreen(isFS) {
23825
+ if (isFS !== undefined) {
23826
+ this.isFullscreen_ = !!isFS;
23827
+ return;
23828
+ }
23829
+ return !!this.isFullscreen_;
23830
+ };
23831
+
23832
+ /**
23833
+ * Increase the size of the video to full screen
23834
+ * In some browsers, full screen is not supported natively, so it enters
23835
+ * "full window mode", where the video fills the browser window.
23836
+ * In browsers and devices that support native full screen, sometimes the
23837
+ * browser's default controls will be shown, and not the Video.js custom skin.
23838
+ * This includes most mobile devices (iOS, Android) and older versions of
23839
+ * Safari.
23840
+ *
23841
+ * @fires Player#fullscreenchange
23842
+ */
23843
+
23844
+
23845
+ Player.prototype.requestFullscreen = function requestFullscreen() {
23846
+ var fsApi = FullscreenApi;
23847
+
23848
+ this.isFullscreen(true);
23849
+
23850
+ if (fsApi.requestFullscreen) {
23851
+ // the browser supports going fullscreen at the element level so we can
23852
+ // take the controls fullscreen as well as the video
23853
+
23854
+ // Trigger fullscreenchange event after change
23855
+ // We have to specifically add this each time, and remove
23856
+ // when canceling fullscreen. Otherwise if there's multiple
23857
+ // players on a page, they would all be reacting to the same fullscreen
23858
+ // events
23859
+ on(document_1, fsApi.fullscreenchange, bind(this, function documentFullscreenChange(e) {
23860
+ this.isFullscreen(document_1[fsApi.fullscreenElement]);
23861
+
23862
+ // If cancelling fullscreen, remove event listener.
23863
+ if (this.isFullscreen() === false) {
23864
+ off(document_1, fsApi.fullscreenchange, documentFullscreenChange);
23865
+ }
23866
+ /**
23867
+ * @event Player#fullscreenchange
23868
+ * @type {EventTarget~Event}
23869
+ */
23870
+ this.trigger('fullscreenchange');
23871
+ }));
23872
+
23873
+ this.el_[fsApi.requestFullscreen]();
23874
+ } else if (this.tech_.supportsFullScreen()) {
23875
+ // we can't take the video.js controls fullscreen but we can go fullscreen
23876
+ // with native controls
23877
+ this.techCall_('enterFullScreen');
23878
+ } else {
23879
+ // fullscreen isn't supported so we'll just stretch the video element to
23880
+ // fill the viewport
23881
+ this.enterFullWindow();
23882
+ /**
23883
+ * @event Player#fullscreenchange
23884
+ * @type {EventTarget~Event}
23885
+ */
23886
+ this.trigger('fullscreenchange');
23887
+ }
23888
+ };
23889
+
23890
+ /**
23891
+ * Return the video to its normal size after having been in full screen mode
23892
+ *
23893
+ * @fires Player#fullscreenchange
23894
+ */
23895
+
23896
+
23897
+ Player.prototype.exitFullscreen = function exitFullscreen() {
23898
+ var fsApi = FullscreenApi;
23899
+
23900
+ this.isFullscreen(false);
23901
+
23902
+ // Check for browser element fullscreen support
23903
+ if (fsApi.requestFullscreen) {
23904
+ document_1[fsApi.exitFullscreen]();
23905
+ } else if (this.tech_.supportsFullScreen()) {
23906
+ this.techCall_('exitFullScreen');
23907
+ } else {
23908
+ this.exitFullWindow();
23909
+ /**
23910
+ * @event Player#fullscreenchange
23911
+ * @type {EventTarget~Event}
23912
+ */
23913
+ this.trigger('fullscreenchange');
23914
+ }
23915
+ };
23916
+
23917
+ /**
23918
+ * When fullscreen isn't supported we can stretch the
23919
+ * video container to as wide as the browser will let us.
23920
+ *
23921
+ * @fires Player#enterFullWindow
23922
+ */
23923
+
23924
+
23925
+ Player.prototype.enterFullWindow = function enterFullWindow() {
23926
+ this.isFullWindow = true;
23927
+
23928
+ // Storing original doc overflow value to return to when fullscreen is off
23929
+ this.docOrigOverflow = document_1.documentElement.style.overflow;
23930
+
23931
+ // Add listener for esc key to exit fullscreen
23932
+ on(document_1, 'keydown', bind(this, this.fullWindowOnEscKey));
23933
+
23934
+ // Hide any scroll bars
23935
+ document_1.documentElement.style.overflow = 'hidden';
23936
+
23937
+ // Apply fullscreen styles
23938
+ addClass(document_1.body, 'vjs-full-window');
23939
+
23940
+ /**
23941
+ * @event Player#enterFullWindow
23942
+ * @type {EventTarget~Event}
23943
+ */
23944
+ this.trigger('enterFullWindow');
23945
+ };
23946
+
23947
+ /**
23948
+ * Check for call to either exit full window or
23949
+ * full screen on ESC key
23950
+ *
23951
+ * @param {string} event
23952
+ * Event to check for key press
23953
+ */
23954
+
23955
+
23956
+ Player.prototype.fullWindowOnEscKey = function fullWindowOnEscKey(event) {
23957
+ if (event.keyCode === 27) {
23958
+ if (this.isFullscreen() === true) {
23959
+ this.exitFullscreen();
23960
+ } else {
23961
+ this.exitFullWindow();
23962
+ }
23963
+ }
23964
+ };
23965
+
23966
+ /**
23967
+ * Exit full window
23968
+ *
23969
+ * @fires Player#exitFullWindow
23970
+ */
23971
+
23972
+
23973
+ Player.prototype.exitFullWindow = function exitFullWindow() {
23974
+ this.isFullWindow = false;
23975
+ off(document_1, 'keydown', this.fullWindowOnEscKey);
23976
+
23977
+ // Unhide scroll bars.
23978
+ document_1.documentElement.style.overflow = this.docOrigOverflow;
23979
+
23980
+ // Remove fullscreen styles
23981
+ removeClass(document_1.body, 'vjs-full-window');
23982
+
23983
+ // Resize the box, controller, and poster to original sizes
23984
+ // this.positionAll();
23985
+ /**
23986
+ * @event Player#exitFullWindow
23987
+ * @type {EventTarget~Event}
23988
+ */
23989
+ this.trigger('exitFullWindow');
23990
+ };
23991
+
23992
+ /**
23993
+ * Check whether the player can play a given mimetype
23994
+ *
23995
+ * @see https://www.w3.org/TR/2011/WD-html5-20110113/video.html#dom-navigator-canplaytype
23996
+ *
23997
+ * @param {string} type
23998
+ * The mimetype to check
23999
+ *
24000
+ * @return {string}
24001
+ * 'probably', 'maybe', or '' (empty string)
24002
+ */
24003
+
24004
+
24005
+ Player.prototype.canPlayType = function canPlayType(type) {
24006
+ var can = void 0;
24007
+
24008
+ // Loop through each playback technology in the options order
24009
+ for (var i = 0, j = this.options_.techOrder; i < j.length; i++) {
24010
+ var techName = j[i];
24011
+ var tech = Tech.getTech(techName);
24012
+
24013
+ // Support old behavior of techs being registered as components.
24014
+ // Remove once that deprecated behavior is removed.
24015
+ if (!tech) {
24016
+ tech = Component.getComponent(techName);
24017
+ }
24018
+
24019
+ // Check if the current tech is defined before continuing
24020
+ if (!tech) {
24021
+ log$1.error('The "' + techName + '" tech is undefined. Skipped browser support check for that tech.');
24022
+ continue;
24023
+ }
24024
+
24025
+ // Check if the browser supports this technology
24026
+ if (tech.isSupported()) {
24027
+ can = tech.canPlayType(type);
24028
+
24029
+ if (can) {
24030
+ return can;
24031
+ }
24032
+ }
24033
+ }
24034
+
24035
+ return '';
24036
+ };
24037
+
24038
+ /**
24039
+ * Select source based on tech-order or source-order
24040
+ * Uses source-order selection if `options.sourceOrder` is truthy. Otherwise,
24041
+ * defaults to tech-order selection
24042
+ *
24043
+ * @param {Array} sources
24044
+ * The sources for a media asset
24045
+ *
24046
+ * @return {Object|boolean}
24047
+ * Object of source and tech order or false
24048
+ */
24049
+
24050
+
24051
+ Player.prototype.selectSource = function selectSource(sources) {
24052
+ var _this8 = this;
24053
+
24054
+ // Get only the techs specified in `techOrder` that exist and are supported by the
24055
+ // current platform
24056
+ var techs = this.options_.techOrder.map(function (techName) {
24057
+ return [techName, Tech.getTech(techName)];
24058
+ }).filter(function (_ref) {
24059
+ var techName = _ref[0],
24060
+ tech = _ref[1];
24061
+
24062
+ // Check if the current tech is defined before continuing
24063
+ if (tech) {
24064
+ // Check if the browser supports this technology
24065
+ return tech.isSupported();
24066
+ }
24067
+
24068
+ log$1.error('The "' + techName + '" tech is undefined. Skipped browser support check for that tech.');
24069
+ return false;
24070
+ });
24071
+
24072
+ // Iterate over each `innerArray` element once per `outerArray` element and execute
24073
+ // `tester` with both. If `tester` returns a non-falsy value, exit early and return
24074
+ // that value.
24075
+ var findFirstPassingTechSourcePair = function findFirstPassingTechSourcePair(outerArray, innerArray, tester) {
24076
+ var found = void 0;
24077
+
24078
+ outerArray.some(function (outerChoice) {
24079
+ return innerArray.some(function (innerChoice) {
24080
+ found = tester(outerChoice, innerChoice);
24081
+
24082
+ if (found) {
24083
+ return true;
24084
+ }
24085
+ });
24086
+ });
24087
+
24088
+ return found;
24089
+ };
24090
+
24091
+ var foundSourceAndTech = void 0;
24092
+ var flip = function flip(fn) {
24093
+ return function (a, b) {
24094
+ return fn(b, a);
24095
+ };
24096
+ };
24097
+ var finder = function finder(_ref2, source) {
24098
+ var techName = _ref2[0],
24099
+ tech = _ref2[1];
24100
+
24101
+ if (tech.canPlaySource(source, _this8.options_[techName.toLowerCase()])) {
24102
+ return { source: source, tech: techName };
24103
+ }
24104
+ };
24105
+
24106
+ // Depending on the truthiness of `options.sourceOrder`, we swap the order of techs and sources
24107
+ // to select from them based on their priority.
24108
+ if (this.options_.sourceOrder) {
24109
+ // Source-first ordering
24110
+ foundSourceAndTech = findFirstPassingTechSourcePair(sources, techs, flip(finder));
24111
+ } else {
24112
+ // Tech-first ordering
24113
+ foundSourceAndTech = findFirstPassingTechSourcePair(techs, sources, finder);
24114
+ }
24115
+
24116
+ return foundSourceAndTech || false;
24117
+ };
24118
+
24119
+ /**
24120
+ * Get or set the video source.
24121
+ *
24122
+ * @param {Tech~SourceObject|Tech~SourceObject[]|string} [source]
24123
+ * A SourceObject, an array of SourceObjects, or a string referencing
24124
+ * a URL to a media source. It is _highly recommended_ that an object
24125
+ * or array of objects is used here, so that source selection
24126
+ * algorithms can take the `type` into account.
24127
+ *
24128
+ * If not provided, this method acts as a getter.
24129
+ *
24130
+ * @return {string|undefined}
24131
+ * If the `source` argument is missing, returns the current source
24132
+ * URL. Otherwise, returns nothing/undefined.
24133
+ */
24134
+
24135
+
24136
+ Player.prototype.src = function src(source) {
24137
+ var _this9 = this;
24138
+
24139
+ // getter usage
24140
+ if (typeof source === 'undefined') {
24141
+ return this.cache_.src || '';
24142
+ }
24143
+ // filter out invalid sources and turn our source into
24144
+ // an array of source objects
24145
+ var sources = filterSource(source);
24146
+
24147
+ // if a source was passed in then it is invalid because
24148
+ // it was filtered to a zero length Array. So we have to
24149
+ // show an error
24150
+ if (!sources.length) {
24151
+ this.setTimeout(function () {
24152
+ this.error({ code: 4, message: this.localize(this.options_.notSupportedMessage) });
24153
+ }, 0);
24154
+ return;
24155
+ }
24156
+
24157
+ // intial sources
24158
+ this.changingSrc_ = true;
24159
+
24160
+ this.cache_.sources = sources;
24161
+ this.updateSourceCaches_(sources[0]);
24162
+
24163
+ // middlewareSource is the source after it has been changed by middleware
24164
+ setSource(this, sources[0], function (middlewareSource, mws) {
24165
+ _this9.middleware_ = mws;
24166
+
24167
+ // since sourceSet is async we have to update the cache again after we select a source since
24168
+ // the source that is selected could be out of order from the cache update above this callback.
24169
+ _this9.cache_.sources = sources;
24170
+ _this9.updateSourceCaches_(middlewareSource);
24171
+
24172
+ var err = _this9.src_(middlewareSource);
24173
+
24174
+ if (err) {
24175
+ if (sources.length > 1) {
24176
+ return _this9.src(sources.slice(1));
24177
+ }
24178
+
24179
+ _this9.changingSrc_ = false;
24180
+
24181
+ // We need to wrap this in a timeout to give folks a chance to add error event handlers
24182
+ _this9.setTimeout(function () {
24183
+ this.error({ code: 4, message: this.localize(this.options_.notSupportedMessage) });
24184
+ }, 0);
24185
+
24186
+ // we could not find an appropriate tech, but let's still notify the delegate that this is it
24187
+ // this needs a better comment about why this is needed
24188
+ _this9.triggerReady();
24189
+
24190
+ return;
24191
+ }
24192
+
24193
+ setTech(mws, _this9.tech_);
24194
+ });
24195
+ };
24196
+
24197
+ /**
24198
+ * Set the source object on the tech, returns a boolean that indicates whether
24199
+ * there is a tech that can play the source or not
24200
+ *
24201
+ * @param {Tech~SourceObject} source
24202
+ * The source object to set on the Tech
24203
+ *
24204
+ * @return {Boolean}
24205
+ * - True if there is no Tech to playback this source
24206
+ * - False otherwise
24207
+ *
24208
+ * @private
24209
+ */
24210
+
24211
+
24212
+ Player.prototype.src_ = function src_(source) {
24213
+ var _this10 = this;
24214
+
24215
+ var sourceTech = this.selectSource([source]);
24216
+
24217
+ if (!sourceTech) {
24218
+ return true;
24219
+ }
24220
+
24221
+ if (!titleCaseEquals(sourceTech.tech, this.techName_)) {
24222
+ this.changingSrc_ = true;
24223
+ // load this technology with the chosen source
24224
+ this.loadTech_(sourceTech.tech, sourceTech.source);
24225
+ this.tech_.ready(function () {
24226
+ _this10.changingSrc_ = false;
24227
+ });
24228
+ return false;
24229
+ }
24230
+
24231
+ // wait until the tech is ready to set the source
24232
+ // and set it synchronously if possible (#2326)
24233
+ this.ready(function () {
24234
+
24235
+ // The setSource tech method was added with source handlers
24236
+ // so older techs won't support it
24237
+ // We need to check the direct prototype for the case where subclasses
24238
+ // of the tech do not support source handlers
24239
+ if (this.tech_.constructor.prototype.hasOwnProperty('setSource')) {
24240
+ this.techCall_('setSource', source);
24241
+ } else {
24242
+ this.techCall_('src', source.src);
24243
+ }
24244
+
24245
+ this.changingSrc_ = false;
24246
+ }, true);
24247
+
24248
+ return false;
24249
+ };
24250
+
24251
+ /**
24252
+ * Begin loading the src data.
24253
+ */
24254
+
24255
+
24256
+ Player.prototype.load = function load() {
24257
+ this.techCall_('load');
24258
+ };
24259
+
24260
+ /**
24261
+ * Reset the player. Loads the first tech in the techOrder,
24262
+ * and calls `reset` on the tech`.
24263
+ */
24264
+
24265
+
24266
+ Player.prototype.reset = function reset() {
24267
+ this.loadTech_(this.options_.techOrder[0], null);
24268
+ this.techCall_('reset');
24269
+ };
24270
+
24271
+ /**
24272
+ * Returns all of the current source objects.
24273
+ *
24274
+ * @return {Tech~SourceObject[]}
24275
+ * The current source objects
24276
+ */
24277
+
24278
+
24279
+ Player.prototype.currentSources = function currentSources() {
24280
+ var source = this.currentSource();
24281
+ var sources = [];
24282
+
24283
+ // assume `{}` or `{ src }`
24284
+ if (Object.keys(source).length !== 0) {
24285
+ sources.push(source);
24286
+ }
24287
+
24288
+ return this.cache_.sources || sources;
24289
+ };
24290
+
24291
+ /**
24292
+ * Returns the current source object.
24293
+ *
24294
+ * @return {Tech~SourceObject}
24295
+ * The current source object
24296
+ */
24297
+
24298
+
24299
+ Player.prototype.currentSource = function currentSource() {
24300
+ return this.cache_.source || {};
24301
+ };
24302
+
24303
+ /**
24304
+ * Returns the fully qualified URL of the current source value e.g. http://mysite.com/video.mp4
24305
+ * Can be used in conjunction with `currentType` to assist in rebuilding the current source object.
24306
+ *
24307
+ * @return {string}
24308
+ * The current source
24309
+ */
24310
+
24311
+
24312
+ Player.prototype.currentSrc = function currentSrc() {
24313
+ return this.currentSource() && this.currentSource().src || '';
24314
+ };
24315
+
24316
+ /**
24317
+ * Get the current source type e.g. video/mp4
24318
+ * This can allow you rebuild the current source object so that you could load the same
24319
+ * source and tech later
24320
+ *
24321
+ * @return {string}
24322
+ * The source MIME type
24323
+ */
24324
+
24325
+
24326
+ Player.prototype.currentType = function currentType() {
24327
+ return this.currentSource() && this.currentSource().type || '';
24328
+ };
24329
+
24330
+ /**
24331
+ * Get or set the preload attribute
24332
+ *
24333
+ * @param {boolean} [value]
24334
+ * - true means that we should preload
24335
+ * - false means that we should not preload
24336
+ *
24337
+ * @return {string}
24338
+ * The preload attribute value when getting
24339
+ */
24340
+
24341
+
24342
+ Player.prototype.preload = function preload(value) {
24343
+ if (value !== undefined) {
24344
+ this.techCall_('setPreload', value);
24345
+ this.options_.preload = value;
24346
+ return;
24347
+ }
24348
+ return this.techGet_('preload');
24349
+ };
24350
+
24351
+ /**
24352
+ * Get or set the autoplay option. When this is a boolean it will
24353
+ * modify the attribute on the tech. When this is a string the attribute on
24354
+ * the tech will be removed and `Player` will handle autoplay on loadstarts.
24355
+ *
24356
+ * @param {boolean|string} [value]
24357
+ * - true: autoplay using the browser behavior
24358
+ * - false: do not autoplay
24359
+ * - 'play': call play() on every loadstart
24360
+ * - 'muted': call muted() then play() on every loadstart
24361
+ * - 'any': call play() on every loadstart. if that fails call muted() then play().
24362
+ * - *: values other than those listed here will be set `autoplay` to true
24363
+ *
24364
+ * @return {boolean|string}
24365
+ * The current value of autoplay when getting
24366
+ */
24367
+
24368
+
24369
+ Player.prototype.autoplay = function autoplay(value) {
24370
+ // getter usage
24371
+ if (value === undefined) {
24372
+ return this.options_.autoplay || false;
24373
+ }
24374
+
24375
+ var techAutoplay = void 0;
24376
+
24377
+ // if the value is a valid string set it to that
24378
+ if (typeof value === 'string' && /(any|play|muted)/.test(value)) {
24379
+ this.options_.autoplay = value;
24380
+ this.manualAutoplay_(value);
24381
+ techAutoplay = false;
24382
+
24383
+ // any falsy value sets autoplay to false in the browser,
24384
+ // lets do the same
24385
+ } else if (!value) {
24386
+ this.options_.autoplay = false;
24387
+
24388
+ // any other value (ie truthy) sets autoplay to true
24389
+ } else {
24390
+ this.options_.autoplay = true;
24391
+ }
24392
+
24393
+ techAutoplay = techAutoplay || this.options_.autoplay;
24394
+
24395
+ // if we don't have a tech then we do not queue up
24396
+ // a setAutoplay call on tech ready. We do this because the
24397
+ // autoplay option will be passed in the constructor and we
24398
+ // do not need to set it twice
24399
+ if (this.tech_) {
24400
+ this.techCall_('setAutoplay', techAutoplay);
24401
+ }
24402
+ };
24403
+
24404
+ /**
24405
+ * Set or unset the playsinline attribute.
24406
+ * Playsinline tells the browser that non-fullscreen playback is preferred.
24407
+ *
24408
+ * @param {boolean} [value]
24409
+ * - true means that we should try to play inline by default
24410
+ * - false means that we should use the browser's default playback mode,
24411
+ * which in most cases is inline. iOS Safari is a notable exception
24412
+ * and plays fullscreen by default.
24413
+ *
24414
+ * @return {string|Player}
24415
+ * - the current value of playsinline
24416
+ * - the player when setting
24417
+ *
24418
+ * @see [Spec]{@link https://html.spec.whatwg.org/#attr-video-playsinline}
24419
+ */
24420
+
24421
+
24422
+ Player.prototype.playsinline = function playsinline(value) {
24423
+ if (value !== undefined) {
24424
+ this.techCall_('setPlaysinline', value);
24425
+ this.options_.playsinline = value;
24426
+ return this;
24427
+ }
24428
+ return this.techGet_('playsinline');
24429
+ };
24430
+
24431
+ /**
24432
+ * Get or set the loop attribute on the video element.
24433
+ *
24434
+ * @param {boolean} [value]
24435
+ * - true means that we should loop the video
24436
+ * - false means that we should not loop the video
24437
+ *
24438
+ * @return {string}
24439
+ * The current value of loop when getting
24440
+ */
24441
+
24442
+
24443
+ Player.prototype.loop = function loop(value) {
24444
+ if (value !== undefined) {
24445
+ this.techCall_('setLoop', value);
24446
+ this.options_.loop = value;
24447
+ return;
24448
+ }
24449
+ return this.techGet_('loop');
24450
+ };
24451
+
24452
+ /**
24453
+ * Get or set the poster image source url
24454
+ *
24455
+ * @fires Player#posterchange
24456
+ *
24457
+ * @param {string} [src]
24458
+ * Poster image source URL
24459
+ *
24460
+ * @return {string}
24461
+ * The current value of poster when getting
24462
+ */
24463
+
24464
+
24465
+ Player.prototype.poster = function poster(src) {
24466
+ if (src === undefined) {
24467
+ return this.poster_;
24468
+ }
24469
+
24470
+ // The correct way to remove a poster is to set as an empty string
24471
+ // other falsey values will throw errors
24472
+ if (!src) {
24473
+ src = '';
24474
+ }
24475
+
24476
+ if (src === this.poster_) {
24477
+ return;
24478
+ }
24479
+
24480
+ // update the internal poster variable
24481
+ this.poster_ = src;
24482
+
24483
+ // update the tech's poster
24484
+ this.techCall_('setPoster', src);
24485
+
24486
+ this.isPosterFromTech_ = false;
24487
+
24488
+ // alert components that the poster has been set
24489
+ /**
24490
+ * This event fires when the poster image is changed on the player.
24491
+ *
24492
+ * @event Player#posterchange
24493
+ * @type {EventTarget~Event}
24494
+ */
24495
+ this.trigger('posterchange');
24496
+ };
24497
+
24498
+ /**
24499
+ * Some techs (e.g. YouTube) can provide a poster source in an
24500
+ * asynchronous way. We want the poster component to use this
24501
+ * poster source so that it covers up the tech's controls.
24502
+ * (YouTube's play button). However we only want to use this
24503
+ * source if the player user hasn't set a poster through
24504
+ * the normal APIs.
24505
+ *
24506
+ * @fires Player#posterchange
24507
+ * @listens Tech#posterchange
24508
+ * @private
24509
+ */
24510
+
24511
+
24512
+ Player.prototype.handleTechPosterChange_ = function handleTechPosterChange_() {
24513
+ if ((!this.poster_ || this.options_.techCanOverridePoster) && this.tech_ && this.tech_.poster) {
24514
+ var newPoster = this.tech_.poster() || '';
24515
+
24516
+ if (newPoster !== this.poster_) {
24517
+ this.poster_ = newPoster;
24518
+ this.isPosterFromTech_ = true;
24519
+
24520
+ // Let components know the poster has changed
24521
+ this.trigger('posterchange');
24522
+ }
24523
+ }
24524
+ };
24525
+
24526
+ /**
24527
+ * Get or set whether or not the controls are showing.
24528
+ *
24529
+ * @fires Player#controlsenabled
24530
+ *
24531
+ * @param {boolean} [bool]
24532
+ * - true to turn controls on
24533
+ * - false to turn controls off
24534
+ *
24535
+ * @return {boolean}
24536
+ * The current value of controls when getting
24537
+ */
24538
+
24539
+
24540
+ Player.prototype.controls = function controls(bool) {
24541
+ if (bool === undefined) {
24542
+ return !!this.controls_;
24543
+ }
24544
+
24545
+ bool = !!bool;
24546
+
24547
+ // Don't trigger a change event unless it actually changed
24548
+ if (this.controls_ === bool) {
24549
+ return;
24550
+ }
24551
+
24552
+ this.controls_ = bool;
24553
+
24554
+ if (this.usingNativeControls()) {
24555
+ this.techCall_('setControls', bool);
24556
+ }
24557
+
24558
+ if (this.controls_) {
24559
+ this.removeClass('vjs-controls-disabled');
24560
+ this.addClass('vjs-controls-enabled');
24561
+ /**
24562
+ * @event Player#controlsenabled
24563
+ * @type {EventTarget~Event}
24564
+ */
24565
+ this.trigger('controlsenabled');
24566
+ if (!this.usingNativeControls()) {
24567
+ this.addTechControlsListeners_();
24568
+ }
24569
+ } else {
24570
+ this.removeClass('vjs-controls-enabled');
24571
+ this.addClass('vjs-controls-disabled');
24572
+ /**
24573
+ * @event Player#controlsdisabled
24574
+ * @type {EventTarget~Event}
24575
+ */
24576
+ this.trigger('controlsdisabled');
24577
+ if (!this.usingNativeControls()) {
24578
+ this.removeTechControlsListeners_();
24579
+ }
24580
+ }
24581
+ };
24582
+
24583
+ /**
24584
+ * Toggle native controls on/off. Native controls are the controls built into
24585
+ * devices (e.g. default iPhone controls), Flash, or other techs
24586
+ * (e.g. Vimeo Controls)
24587
+ * **This should only be set by the current tech, because only the tech knows
24588
+ * if it can support native controls**
24589
+ *
24590
+ * @fires Player#usingnativecontrols
24591
+ * @fires Player#usingcustomcontrols
24592
+ *
24593
+ * @param {boolean} [bool]
24594
+ * - true to turn native controls on
24595
+ * - false to turn native controls off
24596
+ *
24597
+ * @return {boolean}
24598
+ * The current value of native controls when getting
24599
+ */
24600
+
24601
+
24602
+ Player.prototype.usingNativeControls = function usingNativeControls(bool) {
24603
+ if (bool === undefined) {
24604
+ return !!this.usingNativeControls_;
24605
+ }
24606
+
24607
+ bool = !!bool;
24608
+
24609
+ // Don't trigger a change event unless it actually changed
24610
+ if (this.usingNativeControls_ === bool) {
24611
+ return;
24612
+ }
24613
+
24614
+ this.usingNativeControls_ = bool;
24615
+
24616
+ if (this.usingNativeControls_) {
24617
+ this.addClass('vjs-using-native-controls');
24618
+
24619
+ /**
24620
+ * player is using the native device controls
24621
+ *
24622
+ * @event Player#usingnativecontrols
24623
+ * @type {EventTarget~Event}
24624
+ */
24625
+ this.trigger('usingnativecontrols');
24626
+ } else {
24627
+ this.removeClass('vjs-using-native-controls');
24628
+
24629
+ /**
24630
+ * player is using the custom HTML controls
24631
+ *
24632
+ * @event Player#usingcustomcontrols
24633
+ * @type {EventTarget~Event}
24634
+ */
24635
+ this.trigger('usingcustomcontrols');
24636
+ }
24637
+ };
24638
+
24639
+ /**
24640
+ * Set or get the current MediaError
24641
+ *
24642
+ * @fires Player#error
24643
+ *
24644
+ * @param {MediaError|string|number} [err]
24645
+ * A MediaError or a string/number to be turned
24646
+ * into a MediaError
24647
+ *
24648
+ * @return {MediaError|null}
24649
+ * The current MediaError when getting (or null)
24650
+ */
24651
+
24652
+
24653
+ Player.prototype.error = function error(err) {
24654
+ if (err === undefined) {
24655
+ return this.error_ || null;
24656
+ }
24657
+
24658
+ // restoring to default
24659
+ if (err === null) {
24660
+ this.error_ = err;
24661
+ this.removeClass('vjs-error');
24662
+ if (this.errorDisplay) {
24663
+ this.errorDisplay.close();
24664
+ }
24665
+ return;
24666
+ }
24667
+
24668
+ this.error_ = new MediaError(err);
24669
+
24670
+ // add the vjs-error classname to the player
24671
+ this.addClass('vjs-error');
24672
+
24673
+ // log the name of the error type and any message
24674
+ // IE11 logs "[object object]" and required you to expand message to see error object
24675
+ log$1.error('(CODE:' + this.error_.code + ' ' + MediaError.errorTypes[this.error_.code] + ')', this.error_.message, this.error_);
24676
+
24677
+ /**
24678
+ * @event Player#error
24679
+ * @type {EventTarget~Event}
24680
+ */
24681
+ this.trigger('error');
24682
+
24683
+ return;
24684
+ };
24685
+
24686
+ /**
24687
+ * Report user activity
24688
+ *
24689
+ * @param {Object} event
24690
+ * Event object
24691
+ */
24692
+
24693
+
24694
+ Player.prototype.reportUserActivity = function reportUserActivity(event) {
24695
+ this.userActivity_ = true;
24696
+ };
24697
+
24698
+ /**
24699
+ * Get/set if user is active
24700
+ *
24701
+ * @fires Player#useractive
24702
+ * @fires Player#userinactive
24703
+ *
24704
+ * @param {boolean} [bool]
24705
+ * - true if the user is active
24706
+ * - false if the user is inactive
24707
+ *
24708
+ * @return {boolean}
24709
+ * The current value of userActive when getting
24710
+ */
24711
+
24712
+
24713
+ Player.prototype.userActive = function userActive(bool) {
24714
+ if (bool === undefined) {
24715
+ return this.userActive_;
24716
+ }
24717
+
24718
+ bool = !!bool;
24719
+
24720
+ if (bool === this.userActive_) {
24721
+ return;
24722
+ }
24723
+
24724
+ this.userActive_ = bool;
24725
+
24726
+ if (this.userActive_) {
24727
+ this.userActivity_ = true;
24728
+ this.removeClass('vjs-user-inactive');
24729
+ this.addClass('vjs-user-active');
24730
+ /**
24731
+ * @event Player#useractive
24732
+ * @type {EventTarget~Event}
24733
+ */
24734
+ this.trigger('useractive');
24735
+ return;
24736
+ }
24737
+
24738
+ // Chrome/Safari/IE have bugs where when you change the cursor it can
24739
+ // trigger a mousemove event. This causes an issue when you're hiding
24740
+ // the cursor when the user is inactive, and a mousemove signals user
24741
+ // activity. Making it impossible to go into inactive mode. Specifically
24742
+ // this happens in fullscreen when we really need to hide the cursor.
24743
+ //
24744
+ // When this gets resolved in ALL browsers it can be removed
24745
+ // https://code.google.com/p/chromium/issues/detail?id=103041
24746
+ if (this.tech_) {
24747
+ this.tech_.one('mousemove', function (e) {
24748
+ e.stopPropagation();
24749
+ e.preventDefault();
24750
+ });
24751
+ }
24752
+
24753
+ this.userActivity_ = false;
24754
+ this.removeClass('vjs-user-active');
24755
+ this.addClass('vjs-user-inactive');
24756
+ /**
24757
+ * @event Player#userinactive
24758
+ * @type {EventTarget~Event}
24759
+ */
24760
+ this.trigger('userinactive');
24761
+ };
24762
+
24763
+ /**
24764
+ * Listen for user activity based on timeout value
24765
+ *
24766
+ * @private
24767
+ */
24768
+
24769
+
24770
+ Player.prototype.listenForUserActivity_ = function listenForUserActivity_() {
24771
+ var mouseInProgress = void 0;
24772
+ var lastMoveX = void 0;
24773
+ var lastMoveY = void 0;
24774
+ var handleActivity = bind(this, this.reportUserActivity);
24775
+
24776
+ var handleMouseMove = function handleMouseMove(e) {
24777
+ // #1068 - Prevent mousemove spamming
24778
+ // Chrome Bug: https://code.google.com/p/chromium/issues/detail?id=366970
24779
+ if (e.screenX !== lastMoveX || e.screenY !== lastMoveY) {
24780
+ lastMoveX = e.screenX;
24781
+ lastMoveY = e.screenY;
24782
+ handleActivity();
24783
+ }
24784
+ };
24785
+
24786
+ var handleMouseDown = function handleMouseDown() {
24787
+ handleActivity();
24788
+ // For as long as the they are touching the device or have their mouse down,
24789
+ // we consider them active even if they're not moving their finger or mouse.
24790
+ // So we want to continue to update that they are active
24791
+ this.clearInterval(mouseInProgress);
24792
+ // Setting userActivity=true now and setting the interval to the same time
24793
+ // as the activityCheck interval (250) should ensure we never miss the
24794
+ // next activityCheck
24795
+ mouseInProgress = this.setInterval(handleActivity, 250);
24796
+ };
24797
+
24798
+ var handleMouseUp = function handleMouseUp(event) {
24799
+ handleActivity();
24800
+ // Stop the interval that maintains activity if the mouse/touch is down
24801
+ this.clearInterval(mouseInProgress);
24802
+ };
24803
+
24804
+ // Any mouse movement will be considered user activity
24805
+ this.on('mousedown', handleMouseDown);
24806
+ this.on('mousemove', handleMouseMove);
24807
+ this.on('mouseup', handleMouseUp);
24808
+
24809
+ // Listen for keyboard navigation
24810
+ // Shouldn't need to use inProgress interval because of key repeat
24811
+ this.on('keydown', handleActivity);
24812
+ this.on('keyup', handleActivity);
24813
+
24814
+ // Run an interval every 250 milliseconds instead of stuffing everything into
24815
+ // the mousemove/touchmove function itself, to prevent performance degradation.
24816
+ // `this.reportUserActivity` simply sets this.userActivity_ to true, which
24817
+ // then gets picked up by this loop
24818
+ // http://ejohn.org/blog/learning-from-twitter/
24819
+ var inactivityTimeout = void 0;
24820
+
24821
+ this.setInterval(function () {
24822
+ // Check to see if mouse/touch activity has happened
24823
+ if (!this.userActivity_) {
24824
+ return;
24825
+ }
24826
+
24827
+ // Reset the activity tracker
24828
+ this.userActivity_ = false;
24829
+
24830
+ // If the user state was inactive, set the state to active
24831
+ this.userActive(true);
24832
+
24833
+ // Clear any existing inactivity timeout to start the timer over
24834
+ this.clearTimeout(inactivityTimeout);
24835
+
24836
+ var timeout = this.options_.inactivityTimeout;
24837
+
24838
+ if (timeout <= 0) {
24839
+ return;
24840
+ }
24841
+
24842
+ // In <timeout> milliseconds, if no more activity has occurred the
24843
+ // user will be considered inactive
24844
+ inactivityTimeout = this.setTimeout(function () {
24845
+ // Protect against the case where the inactivityTimeout can trigger just
24846
+ // before the next user activity is picked up by the activity check loop
24847
+ // causing a flicker
24848
+ if (!this.userActivity_) {
24849
+ this.userActive(false);
24850
+ }
24851
+ }, timeout);
24852
+ }, 250);
24853
+ };
24854
+
24855
+ /**
24856
+ * Gets or sets the current playback rate. A playback rate of
24857
+ * 1.0 represents normal speed and 0.5 would indicate half-speed
24858
+ * playback, for instance.
24859
+ *
24860
+ * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-media-playbackrate
24861
+ *
24862
+ * @param {number} [rate]
24863
+ * New playback rate to set.
24864
+ *
24865
+ * @return {number}
24866
+ * The current playback rate when getting or 1.0
24867
+ */
24868
+
24869
+
24870
+ Player.prototype.playbackRate = function playbackRate(rate) {
24871
+ if (rate !== undefined) {
24872
+ // NOTE: this.cache_.lastPlaybackRate is set from the tech handler
24873
+ // that is registered above
24874
+ this.techCall_('setPlaybackRate', rate);
24875
+ return;
24876
+ }
24877
+
24878
+ if (this.tech_ && this.tech_.featuresPlaybackRate) {
24879
+ return this.cache_.lastPlaybackRate || this.techGet_('playbackRate');
24880
+ }
24881
+ return 1.0;
24882
+ };
24883
+
24884
+ /**
24885
+ * Gets or sets the current default playback rate. A default playback rate of
24886
+ * 1.0 represents normal speed and 0.5 would indicate half-speed playback, for instance.
24887
+ * defaultPlaybackRate will only represent what the initial playbackRate of a video was, not
24888
+ * not the current playbackRate.
24889
+ *
24890
+ * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-media-defaultplaybackrate
24891
+ *
24892
+ * @param {number} [rate]
24893
+ * New default playback rate to set.
24894
+ *
24895
+ * @return {number|Player}
24896
+ * - The default playback rate when getting or 1.0
24897
+ * - the player when setting
24898
+ */
24899
+
24900
+
24901
+ Player.prototype.defaultPlaybackRate = function defaultPlaybackRate(rate) {
24902
+ if (rate !== undefined) {
24903
+ return this.techCall_('setDefaultPlaybackRate', rate);
24904
+ }
24905
+
24906
+ if (this.tech_ && this.tech_.featuresPlaybackRate) {
24907
+ return this.techGet_('defaultPlaybackRate');
24908
+ }
24909
+ return 1.0;
24910
+ };
24911
+
24912
+ /**
24913
+ * Gets or sets the audio flag
24914
+ *
24915
+ * @param {boolean} bool
24916
+ * - true signals that this is an audio player
24917
+ * - false signals that this is not an audio player
24918
+ *
24919
+ * @return {boolean}
24920
+ * The current value of isAudio when getting
24921
+ */
24922
+
24923
+
24924
+ Player.prototype.isAudio = function isAudio(bool) {
24925
+ if (bool !== undefined) {
24926
+ this.isAudio_ = !!bool;
24927
+ return;
24928
+ }
24929
+
24930
+ return !!this.isAudio_;
24931
+ };
24932
+
24933
+ /**
24934
+ * A helper method for adding a {@link TextTrack} to our
24935
+ * {@link TextTrackList}.
24936
+ *
24937
+ * In addition to the W3C settings we allow adding additional info through options.
24938
+ *
24939
+ * @see http://www.w3.org/html/wg/drafts/html/master/embedded-content-0.html#dom-media-addtexttrack
24940
+ *
24941
+ * @param {string} [kind]
24942
+ * the kind of TextTrack you are adding
24943
+ *
24944
+ * @param {string} [label]
24945
+ * the label to give the TextTrack label
24946
+ *
24947
+ * @param {string} [language]
24948
+ * the language to set on the TextTrack
24949
+ *
24950
+ * @return {TextTrack|undefined}
24951
+ * the TextTrack that was added or undefined
24952
+ * if there is no tech
24953
+ */
24954
+
24955
+
24956
+ Player.prototype.addTextTrack = function addTextTrack(kind, label, language) {
24957
+ if (this.tech_) {
24958
+ return this.tech_.addTextTrack(kind, label, language);
24959
+ }
24960
+ };
24961
+
24962
+ /**
24963
+ * Create a remote {@link TextTrack} and an {@link HTMLTrackElement}. It will
24964
+ * automatically removed from the video element whenever the source changes, unless
24965
+ * manualCleanup is set to false.
24966
+ *
24967
+ * @param {Object} options
24968
+ * Options to pass to {@link HTMLTrackElement} during creation. See
24969
+ * {@link HTMLTrackElement} for object properties that you should use.
24970
+ *
24971
+ * @param {boolean} [manualCleanup=true] if set to false, the TextTrack will be
24972
+ *
24973
+ * @return {HtmlTrackElement}
24974
+ * the HTMLTrackElement that was created and added
24975
+ * to the HtmlTrackElementList and the remote
24976
+ * TextTrackList
24977
+ *
24978
+ * @deprecated The default value of the "manualCleanup" parameter will default
24979
+ * to "false" in upcoming versions of Video.js
24980
+ */
24981
+
24982
+
24983
+ Player.prototype.addRemoteTextTrack = function addRemoteTextTrack(options, manualCleanup) {
24984
+ if (this.tech_) {
24985
+ return this.tech_.addRemoteTextTrack(options, manualCleanup);
24986
+ }
24987
+ };
24988
+
24989
+ /**
24990
+ * Remove a remote {@link TextTrack} from the respective
24991
+ * {@link TextTrackList} and {@link HtmlTrackElementList}.
24992
+ *
24993
+ * @param {Object} track
24994
+ * Remote {@link TextTrack} to remove
24995
+ *
24996
+ * @return {undefined}
24997
+ * does not return anything
24998
+ */
24999
+
25000
+
25001
+ Player.prototype.removeRemoteTextTrack = function removeRemoteTextTrack() {
25002
+ var _ref3 = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {},
25003
+ _ref3$track = _ref3.track,
25004
+ track = _ref3$track === undefined ? arguments[0] : _ref3$track;
25005
+
25006
+ // destructure the input into an object with a track argument, defaulting to arguments[0]
25007
+ // default the whole argument to an empty object if nothing was passed in
25008
+
25009
+ if (this.tech_) {
25010
+ return this.tech_.removeRemoteTextTrack(track);
25011
+ }
25012
+ };
25013
+
25014
+ /**
25015
+ * Gets available media playback quality metrics as specified by the W3C's Media
25016
+ * Playback Quality API.
25017
+ *
25018
+ * @see [Spec]{@link https://wicg.github.io/media-playback-quality}
25019
+ *
25020
+ * @return {Object|undefined}
25021
+ * An object with supported media playback quality metrics or undefined if there
25022
+ * is no tech or the tech does not support it.
25023
+ */
25024
+
25025
+
25026
+ Player.prototype.getVideoPlaybackQuality = function getVideoPlaybackQuality() {
25027
+ return this.techGet_('getVideoPlaybackQuality');
25028
+ };
25029
+
25030
+ /**
25031
+ * Get video width
25032
+ *
25033
+ * @return {number}
25034
+ * current video width
25035
+ */
25036
+
25037
+
25038
+ Player.prototype.videoWidth = function videoWidth() {
25039
+ return this.tech_ && this.tech_.videoWidth && this.tech_.videoWidth() || 0;
25040
+ };
25041
+
25042
+ /**
25043
+ * Get video height
25044
+ *
25045
+ * @return {number}
25046
+ * current video height
25047
+ */
25048
+
25049
+
25050
+ Player.prototype.videoHeight = function videoHeight() {
25051
+ return this.tech_ && this.tech_.videoHeight && this.tech_.videoHeight() || 0;
25052
+ };
25053
+
25054
+ /**
25055
+ * The player's language code
25056
+ * NOTE: The language should be set in the player options if you want the
25057
+ * the controls to be built with a specific language. Changing the language
25058
+ * later will not update controls text.
25059
+ *
25060
+ * @param {string} [code]
25061
+ * the language code to set the player to
25062
+ *
25063
+ * @return {string}
25064
+ * The current language code when getting
25065
+ */
25066
+
25067
+
25068
+ Player.prototype.language = function language(code) {
25069
+ if (code === undefined) {
25070
+ return this.language_;
25071
+ }
25072
+
25073
+ this.language_ = String(code).toLowerCase();
25074
+ };
25075
+
25076
+ /**
25077
+ * Get the player's language dictionary
25078
+ * Merge every time, because a newly added plugin might call videojs.addLanguage() at any time
25079
+ * Languages specified directly in the player options have precedence
25080
+ *
25081
+ * @return {Array}
25082
+ * An array of of supported languages
25083
+ */
25084
+
25085
+
25086
+ Player.prototype.languages = function languages() {
25087
+ return mergeOptions(Player.prototype.options_.languages, this.languages_);
25088
+ };
25089
+
25090
+ /**
25091
+ * returns a JavaScript object reperesenting the current track
25092
+ * information. **DOES not return it as JSON**
25093
+ *
25094
+ * @return {Object}
25095
+ * Object representing the current of track info
25096
+ */
25097
+
25098
+
25099
+ Player.prototype.toJSON = function toJSON() {
25100
+ var options = mergeOptions(this.options_);
25101
+ var tracks = options.tracks;
25102
+
25103
+ options.tracks = [];
25104
+
25105
+ for (var i = 0; i < tracks.length; i++) {
25106
+ var track = tracks[i];
25107
+
25108
+ // deep merge tracks and null out player so no circular references
25109
+ track = mergeOptions(track);
25110
+ track.player = undefined;
25111
+ options.tracks[i] = track;
25112
+ }
25113
+
25114
+ return options;
25115
+ };
25116
+
25117
+ /**
25118
+ * Creates a simple modal dialog (an instance of the {@link ModalDialog}
25119
+ * component) that immediately overlays the player with arbitrary
25120
+ * content and removes itself when closed.
25121
+ *
25122
+ * @param {string|Function|Element|Array|null} content
25123
+ * Same as {@link ModalDialog#content}'s param of the same name.
25124
+ * The most straight-forward usage is to provide a string or DOM
25125
+ * element.
25126
+ *
25127
+ * @param {Object} [options]
25128
+ * Extra options which will be passed on to the {@link ModalDialog}.
25129
+ *
25130
+ * @return {ModalDialog}
25131
+ * the {@link ModalDialog} that was created
25132
+ */
25133
+
25134
+
25135
+ Player.prototype.createModal = function createModal(content, options) {
25136
+ var _this11 = this;
25137
+
25138
+ options = options || {};
25139
+ options.content = content || '';
25140
+
25141
+ var modal = new ModalDialog(this, options);
25142
+
25143
+ this.addChild(modal);
25144
+ modal.on('dispose', function () {
25145
+ _this11.removeChild(modal);
25146
+ });
25147
+
25148
+ modal.open();
25149
+ return modal;
25150
+ };
25151
+
25152
+ /**
25153
+ * Gets tag settings
25154
+ *
25155
+ * @param {Element} tag
25156
+ * The player tag
25157
+ *
25158
+ * @return {Object}
25159
+ * An object containing all of the settings
25160
+ * for a player tag
25161
+ */
25162
+
25163
+
25164
+ Player.getTagSettings = function getTagSettings(tag) {
25165
+ var baseOptions = {
25166
+ sources: [],
25167
+ tracks: []
25168
+ };
25169
+
25170
+ var tagOptions = getAttributes(tag);
25171
+ var dataSetup = tagOptions['data-setup'];
25172
+
25173
+ if (hasClass(tag, 'vjs-fluid')) {
25174
+ tagOptions.fluid = true;
25175
+ }
25176
+
25177
+ // Check if data-setup attr exists.
25178
+ if (dataSetup !== null) {
25179
+ // Parse options JSON
25180
+ // If empty string, make it a parsable json object.
25181
+ var _safeParseTuple = tuple(dataSetup || '{}'),
25182
+ err = _safeParseTuple[0],
25183
+ data = _safeParseTuple[1];
25184
+
25185
+ if (err) {
25186
+ log$1.error(err);
25187
+ }
25188
+ assign(tagOptions, data);
25189
+ }
25190
+
25191
+ assign(baseOptions, tagOptions);
25192
+
25193
+ // Get tag children settings
25194
+ if (tag.hasChildNodes()) {
25195
+ var children = tag.childNodes;
25196
+
25197
+ for (var i = 0, j = children.length; i < j; i++) {
25198
+ var child = children[i];
25199
+ // Change case needed: http://ejohn.org/blog/nodename-case-sensitivity/
25200
+ var childName = child.nodeName.toLowerCase();
25201
+
25202
+ if (childName === 'source') {
25203
+ baseOptions.sources.push(getAttributes(child));
25204
+ } else if (childName === 'track') {
25205
+ baseOptions.tracks.push(getAttributes(child));
25206
+ }
25207
+ }
25208
+ }
25209
+
25210
+ return baseOptions;
25211
+ };
25212
+
25213
+ /**
25214
+ * Determine whether or not flexbox is supported
25215
+ *
25216
+ * @return {boolean}
25217
+ * - true if flexbox is supported
25218
+ * - false if flexbox is not supported
25219
+ */
25220
+
25221
+
25222
+ Player.prototype.flexNotSupported_ = function flexNotSupported_() {
25223
+ var elem = document_1.createElement('i');
25224
+
25225
+ // Note: We don't actually use flexBasis (or flexOrder), but it's one of the more
25226
+ // common flex features that we can rely on when checking for flex support.
25227
+ return !('flexBasis' in elem.style || 'webkitFlexBasis' in elem.style || 'mozFlexBasis' in elem.style || 'msFlexBasis' in elem.style ||
25228
+ // IE10-specific (2012 flex spec), available for completeness
25229
+ 'msFlexOrder' in elem.style);
25230
+ };
25231
+
25232
+ return Player;
25233
+ }(Component);
25234
+
25235
+ /**
25236
+ * Get the {@link VideoTrackList}
25237
+ * @link https://html.spec.whatwg.org/multipage/embedded-content.html#videotracklist
25238
+ *
25239
+ * @return {VideoTrackList}
25240
+ * the current video track list
25241
+ *
25242
+ * @method Player.prototype.videoTracks
25243
+ */
25244
+
25245
+ /**
25246
+ * Get the {@link AudioTrackList}
25247
+ * @link https://html.spec.whatwg.org/multipage/embedded-content.html#audiotracklist
25248
+ *
25249
+ * @return {AudioTrackList}
25250
+ * the current audio track list
25251
+ *
25252
+ * @method Player.prototype.audioTracks
25253
+ */
25254
+
25255
+ /**
25256
+ * Get the {@link TextTrackList}
25257
+ *
25258
+ * @link http://www.w3.org/html/wg/drafts/html/master/embedded-content-0.html#dom-media-texttracks
25259
+ *
25260
+ * @return {TextTrackList}
25261
+ * the current text track list
25262
+ *
25263
+ * @method Player.prototype.textTracks
25264
+ */
25265
+
25266
+ /**
25267
+ * Get the remote {@link TextTrackList}
25268
+ *
25269
+ * @return {TextTrackList}
25270
+ * The current remote text track list
25271
+ *
25272
+ * @method Player.prototype.remoteTextTracks
25273
+ */
25274
+
25275
+ /**
25276
+ * Get the remote {@link HtmlTrackElementList} tracks.
25277
+ *
25278
+ * @return {HtmlTrackElementList}
25279
+ * The current remote text track element list
25280
+ *
25281
+ * @method Player.prototype.remoteTextTrackEls
25282
+ */
25283
+
25284
+ ALL.names.forEach(function (name$$1) {
25285
+ var props = ALL[name$$1];
25286
+
25287
+ Player.prototype[props.getterName] = function () {
25288
+ if (this.tech_) {
25289
+ return this.tech_[props.getterName]();
25290
+ }
25291
+
25292
+ // if we have not yet loadTech_, we create {video,audio,text}Tracks_
25293
+ // these will be passed to the tech during loading
25294
+ this[props.privateName] = this[props.privateName] || new props.ListClass();
25295
+ return this[props.privateName];
25296
+ };
25297
+ });
25298
+
25299
+ /**
25300
+ * Global player list
25301
+ *
25302
+ * @type {Object}
25303
+ */
25304
+ Player.players = {};
25305
+
25306
+ var navigator = window_1.navigator;
25307
+
25308
+ /*
25309
+ * Player instance options, surfaced using options
25310
+ * options = Player.prototype.options_
25311
+ * Make changes in options, not here.
25312
+ *
25313
+ * @type {Object}
25314
+ * @private
25315
+ */
25316
+ Player.prototype.options_ = {
25317
+ // Default order of fallback technology
25318
+ techOrder: Tech.defaultTechOrder_,
25319
+
25320
+ html5: {},
25321
+ flash: {},
25322
+
25323
+ // default inactivity timeout
25324
+ inactivityTimeout: 2000,
25325
+
25326
+ // default playback rates
25327
+ playbackRates: [],
25328
+ // Add playback rate selection by adding rates
25329
+ // 'playbackRates': [0.5, 1, 1.5, 2],
25330
+
25331
+ // Included control sets
25332
+ children: ['mediaLoader', 'posterImage', 'textTrackDisplay', 'loadingSpinner', 'bigPlayButton', 'controlBar', 'errorDisplay', 'textTrackSettings', 'resizeManager'],
25333
+
25334
+ language: navigator && (navigator.languages && navigator.languages[0] || navigator.userLanguage || navigator.language) || 'en',
25335
+
25336
+ // locales and their language translations
25337
+ languages: {},
25338
+
25339
+ // Default message to show when a video cannot be played.
25340
+ notSupportedMessage: 'No compatible source was found for this media.'
25341
+ };
25342
+
25343
+ [
25344
+ /**
25345
+ * Returns whether or not the player is in the "ended" state.
25346
+ *
25347
+ * @return {Boolean} True if the player is in the ended state, false if not.
25348
+ * @method Player#ended
25349
+ */
25350
+ 'ended',
25351
+ /**
25352
+ * Returns whether or not the player is in the "seeking" state.
25353
+ *
25354
+ * @return {Boolean} True if the player is in the seeking state, false if not.
25355
+ * @method Player#seeking
25356
+ */
25357
+ 'seeking',
25358
+ /**
25359
+ * Returns the TimeRanges of the media that are currently available
25360
+ * for seeking to.
25361
+ *
25362
+ * @return {TimeRanges} the seekable intervals of the media timeline
25363
+ * @method Player#seekable
25364
+ */
25365
+ 'seekable',
25366
+ /**
25367
+ * Returns the current state of network activity for the element, from
25368
+ * the codes in the list below.
25369
+ * - NETWORK_EMPTY (numeric value 0)
25370
+ * The element has not yet been initialised. All attributes are in
25371
+ * their initial states.
25372
+ * - NETWORK_IDLE (numeric value 1)
25373
+ * The element's resource selection algorithm is active and has
25374
+ * selected a resource, but it is not actually using the network at
25375
+ * this time.
25376
+ * - NETWORK_LOADING (numeric value 2)
25377
+ * The user agent is actively trying to download data.
25378
+ * - NETWORK_NO_SOURCE (numeric value 3)
25379
+ * The element's resource selection algorithm is active, but it has
25380
+ * not yet found a resource to use.
25381
+ *
25382
+ * @see https://html.spec.whatwg.org/multipage/embedded-content.html#network-states
25383
+ * @return {number} the current network activity state
25384
+ * @method Player#networkState
25385
+ */
25386
+ 'networkState',
25387
+ /**
25388
+ * Returns a value that expresses the current state of the element
25389
+ * with respect to rendering the current playback position, from the
25390
+ * codes in the list below.
25391
+ * - HAVE_NOTHING (numeric value 0)
25392
+ * No information regarding the media resource is available.
25393
+ * - HAVE_METADATA (numeric value 1)
25394
+ * Enough of the resource has been obtained that the duration of the
25395
+ * resource is available.
25396
+ * - HAVE_CURRENT_DATA (numeric value 2)
25397
+ * Data for the immediate current playback position is available.
25398
+ * - HAVE_FUTURE_DATA (numeric value 3)
25399
+ * Data for the immediate current playback position is available, as
25400
+ * well as enough data for the user agent to advance the current
25401
+ * playback position in the direction of playback.
25402
+ * - HAVE_ENOUGH_DATA (numeric value 4)
25403
+ * The user agent estimates that enough data is available for
25404
+ * playback to proceed uninterrupted.
25405
+ *
25406
+ * @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-media-readystate
25407
+ * @return {number} the current playback rendering state
25408
+ * @method Player#readyState
25409
+ */
25410
+ 'readyState'].forEach(function (fn) {
25411
+ Player.prototype[fn] = function () {
25412
+ return this.techGet_(fn);
25413
+ };
25414
+ });
25415
+
25416
+ TECH_EVENTS_RETRIGGER.forEach(function (event) {
25417
+ Player.prototype['handleTech' + toTitleCase(event) + '_'] = function () {
25418
+ return this.trigger(event);
25419
+ };
25420
+ });
25421
+
25422
+ /**
25423
+ * Fired when the player has initial duration and dimension information
25424
+ *
25425
+ * @event Player#loadedmetadata
25426
+ * @type {EventTarget~Event}
25427
+ */
25428
+
25429
+ /**
25430
+ * Fired when the player has downloaded data at the current playback position
25431
+ *
25432
+ * @event Player#loadeddata
25433
+ * @type {EventTarget~Event}
25434
+ */
25435
+
25436
+ /**
25437
+ * Fired when the current playback position has changed *
25438
+ * During playback this is fired every 15-250 milliseconds, depending on the
25439
+ * playback technology in use.
25440
+ *
25441
+ * @event Player#timeupdate
25442
+ * @type {EventTarget~Event}
25443
+ */
25444
+
25445
+ /**
25446
+ * Fired when the volume changes
25447
+ *
25448
+ * @event Player#volumechange
25449
+ * @type {EventTarget~Event}
25450
+ */
25451
+
25452
+ /**
25453
+ * Reports whether or not a player has a plugin available.
25454
+ *
25455
+ * This does not report whether or not the plugin has ever been initialized
25456
+ * on this player. For that, [usingPlugin]{@link Player#usingPlugin}.
25457
+ *
25458
+ * @method Player#hasPlugin
25459
+ * @param {string} name
25460
+ * The name of a plugin.
25461
+ *
25462
+ * @return {boolean}
25463
+ * Whether or not this player has the requested plugin available.
25464
+ */
25465
+
25466
+ /**
25467
+ * Reports whether or not a player is using a plugin by name.
25468
+ *
25469
+ * For basic plugins, this only reports whether the plugin has _ever_ been
25470
+ * initialized on this player.
25471
+ *
25472
+ * @method Player#usingPlugin
25473
+ * @param {string} name
25474
+ * The name of a plugin.
25475
+ *
25476
+ * @return {boolean}
25477
+ * Whether or not this player is using the requested plugin.
25478
+ */
25479
+
25480
+ Component.registerComponent('Player', Player);
25481
+
25482
+ /**
25483
+ * @file plugin.js
25484
+ */
25485
+
25486
+ /**
25487
+ * The base plugin name.
25488
+ *
25489
+ * @private
25490
+ * @constant
25491
+ * @type {string}
25492
+ */
25493
+ var BASE_PLUGIN_NAME = 'plugin';
25494
+
25495
+ /**
25496
+ * The key on which a player's active plugins cache is stored.
25497
+ *
25498
+ * @private
25499
+ * @constant
25500
+ * @type {string}
25501
+ */
25502
+ var PLUGIN_CACHE_KEY = 'activePlugins_';
25503
+
25504
+ /**
25505
+ * Stores registered plugins in a private space.
25506
+ *
25507
+ * @private
25508
+ * @type {Object}
25509
+ */
25510
+ var pluginStorage = {};
25511
+
25512
+ /**
25513
+ * Reports whether or not a plugin has been registered.
25514
+ *
25515
+ * @private
25516
+ * @param {string} name
25517
+ * The name of a plugin.
25518
+ *
25519
+ * @returns {boolean}
25520
+ * Whether or not the plugin has been registered.
25521
+ */
25522
+ var pluginExists = function pluginExists(name) {
25523
+ return pluginStorage.hasOwnProperty(name);
25524
+ };
25525
+
25526
+ /**
25527
+ * Get a single registered plugin by name.
25528
+ *
25529
+ * @private
25530
+ * @param {string} name
25531
+ * The name of a plugin.
25532
+ *
25533
+ * @returns {Function|undefined}
25534
+ * The plugin (or undefined).
25535
+ */
25536
+ var getPlugin = function getPlugin(name) {
25537
+ return pluginExists(name) ? pluginStorage[name] : undefined;
25538
+ };
25539
+
25540
+ /**
25541
+ * Marks a plugin as "active" on a player.
25542
+ *
25543
+ * Also, ensures that the player has an object for tracking active plugins.
25544
+ *
25545
+ * @private
25546
+ * @param {Player} player
25547
+ * A Video.js player instance.
25548
+ *
25549
+ * @param {string} name
25550
+ * The name of a plugin.
25551
+ */
25552
+ var markPluginAsActive = function markPluginAsActive(player, name) {
25553
+ player[PLUGIN_CACHE_KEY] = player[PLUGIN_CACHE_KEY] || {};
25554
+ player[PLUGIN_CACHE_KEY][name] = true;
25555
+ };
25556
+
25557
+ /**
25558
+ * Triggers a pair of plugin setup events.
25559
+ *
25560
+ * @private
25561
+ * @param {Player} player
25562
+ * A Video.js player instance.
25563
+ *
25564
+ * @param {Plugin~PluginEventHash} hash
25565
+ * A plugin event hash.
25566
+ *
25567
+ * @param {Boolean} [before]
25568
+ * If true, prefixes the event name with "before". In other words,
25569
+ * use this to trigger "beforepluginsetup" instead of "pluginsetup".
25570
+ */
25571
+ var triggerSetupEvent = function triggerSetupEvent(player, hash, before) {
25572
+ var eventName = (before ? 'before' : '') + 'pluginsetup';
25573
+
25574
+ player.trigger(eventName, hash);
25575
+ player.trigger(eventName + ':' + hash.name, hash);
25576
+ };
25577
+
25578
+ /**
25579
+ * Takes a basic plugin function and returns a wrapper function which marks
25580
+ * on the player that the plugin has been activated.
25581
+ *
25582
+ * @private
25583
+ * @param {string} name
25584
+ * The name of the plugin.
25585
+ *
25586
+ * @param {Function} plugin
25587
+ * The basic plugin.
25588
+ *
25589
+ * @returns {Function}
25590
+ * A wrapper function for the given plugin.
25591
+ */
25592
+ var createBasicPlugin = function createBasicPlugin(name, plugin) {
25593
+ var basicPluginWrapper = function basicPluginWrapper() {
25594
+
25595
+ // We trigger the "beforepluginsetup" and "pluginsetup" events on the player
25596
+ // regardless, but we want the hash to be consistent with the hash provided
25597
+ // for advanced plugins.
25598
+ //
25599
+ // The only potentially counter-intuitive thing here is the `instance` in
25600
+ // the "pluginsetup" event is the value returned by the `plugin` function.
25601
+ triggerSetupEvent(this, { name: name, plugin: plugin, instance: null }, true);
25602
+
25603
+ var instance = plugin.apply(this, arguments);
25604
+
25605
+ markPluginAsActive(this, name);
25606
+ triggerSetupEvent(this, { name: name, plugin: plugin, instance: instance });
25607
+
25608
+ return instance;
25609
+ };
25610
+
25611
+ Object.keys(plugin).forEach(function (prop) {
25612
+ basicPluginWrapper[prop] = plugin[prop];
25613
+ });
25614
+
25615
+ return basicPluginWrapper;
25616
+ };
25617
+
25618
+ /**
25619
+ * Takes a plugin sub-class and returns a factory function for generating
25620
+ * instances of it.
25621
+ *
25622
+ * This factory function will replace itself with an instance of the requested
25623
+ * sub-class of Plugin.
25624
+ *
25625
+ * @private
25626
+ * @param {string} name
25627
+ * The name of the plugin.
25628
+ *
25629
+ * @param {Plugin} PluginSubClass
25630
+ * The advanced plugin.
25631
+ *
25632
+ * @returns {Function}
25633
+ */
25634
+ var createPluginFactory = function createPluginFactory(name, PluginSubClass) {
25635
+
25636
+ // Add a `name` property to the plugin prototype so that each plugin can
25637
+ // refer to itself by name.
25638
+ PluginSubClass.prototype.name = name;
25639
+
25640
+ return function () {
25641
+ triggerSetupEvent(this, { name: name, plugin: PluginSubClass, instance: null }, true);
25642
+
25643
+ for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
25644
+ args[_key] = arguments[_key];
25645
+ }
25646
+
25647
+ var instance = new (Function.prototype.bind.apply(PluginSubClass, [null].concat([this].concat(args))))();
25648
+
25649
+ // The plugin is replaced by a function that returns the current instance.
25650
+ this[name] = function () {
25651
+ return instance;
25652
+ };
25653
+
25654
+ triggerSetupEvent(this, instance.getEventHash());
25655
+
25656
+ return instance;
25657
+ };
25658
+ };
25659
+
25660
+ /**
25661
+ * Parent class for all advanced plugins.
25662
+ *
25663
+ * @mixes module:evented~EventedMixin
25664
+ * @mixes module:stateful~StatefulMixin
25665
+ * @fires Player#beforepluginsetup
25666
+ * @fires Player#beforepluginsetup:$name
25667
+ * @fires Player#pluginsetup
25668
+ * @fires Player#pluginsetup:$name
25669
+ * @listens Player#dispose
25670
+ * @throws {Error}
25671
+ * If attempting to instantiate the base {@link Plugin} class
25672
+ * directly instead of via a sub-class.
25673
+ */
25674
+
25675
+ var Plugin = function () {
25676
+
25677
+ /**
25678
+ * Creates an instance of this class.
25679
+ *
25680
+ * Sub-classes should call `super` to ensure plugins are properly initialized.
25681
+ *
25682
+ * @param {Player} player
25683
+ * A Video.js player instance.
25684
+ */
25685
+ function Plugin(player) {
25686
+ classCallCheck(this, Plugin);
25687
+
25688
+ if (this.constructor === Plugin) {
25689
+ throw new Error('Plugin must be sub-classed; not directly instantiated.');
25690
+ }
25691
+
25692
+ this.player = player;
25693
+
25694
+ // Make this object evented, but remove the added `trigger` method so we
25695
+ // use the prototype version instead.
25696
+ evented(this);
25697
+ delete this.trigger;
25698
+
25699
+ stateful(this, this.constructor.defaultState);
25700
+ markPluginAsActive(player, this.name);
25701
+
25702
+ // Auto-bind the dispose method so we can use it as a listener and unbind
25703
+ // it later easily.
25704
+ this.dispose = bind(this, this.dispose);
25705
+
25706
+ // If the player is disposed, dispose the plugin.
25707
+ player.on('dispose', this.dispose);
25708
+ }
25709
+
25710
+ /**
25711
+ * Get the version of the plugin that was set on <pluginName>.VERSION
25712
+ */
25713
+
25714
+
25715
+ Plugin.prototype.version = function version() {
25716
+ return this.constructor.VERSION;
25717
+ };
25718
+
25719
+ /**
25720
+ * Each event triggered by plugins includes a hash of additional data with
25721
+ * conventional properties.
25722
+ *
25723
+ * This returns that object or mutates an existing hash.
25724
+ *
25725
+ * @param {Object} [hash={}]
25726
+ * An object to be used as event an event hash.
25727
+ *
25728
+ * @returns {Plugin~PluginEventHash}
25729
+ * An event hash object with provided properties mixed-in.
25730
+ */
25731
+
25732
+
25733
+ Plugin.prototype.getEventHash = function getEventHash() {
25734
+ var hash = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
25735
+
25736
+ hash.name = this.name;
25737
+ hash.plugin = this.constructor;
25738
+ hash.instance = this;
25739
+ return hash;
25740
+ };
25741
+
25742
+ /**
25743
+ * Triggers an event on the plugin object and overrides
25744
+ * {@link module:evented~EventedMixin.trigger|EventedMixin.trigger}.
25745
+ *
25746
+ * @param {string|Object} event
25747
+ * An event type or an object with a type property.
25748
+ *
25749
+ * @param {Object} [hash={}]
25750
+ * Additional data hash to merge with a
25751
+ * {@link Plugin~PluginEventHash|PluginEventHash}.
25752
+ *
25753
+ * @returns {boolean}
25754
+ * Whether or not default was prevented.
25755
+ */
25756
+
25757
+
25758
+ Plugin.prototype.trigger = function trigger$$1(event) {
25759
+ var hash = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
25760
+
25761
+ return trigger(this.eventBusEl_, event, this.getEventHash(hash));
25762
+ };
25763
+
25764
+ /**
25765
+ * Handles "statechanged" events on the plugin. No-op by default, override by
25766
+ * subclassing.
25767
+ *
25768
+ * @abstract
25769
+ * @param {Event} e
25770
+ * An event object provided by a "statechanged" event.
25771
+ *
25772
+ * @param {Object} e.changes
25773
+ * An object describing changes that occurred with the "statechanged"
25774
+ * event.
25775
+ */
25776
+
25777
+
25778
+ Plugin.prototype.handleStateChanged = function handleStateChanged(e) {};
25779
+
25780
+ /**
25781
+ * Disposes a plugin.
25782
+ *
25783
+ * Subclasses can override this if they want, but for the sake of safety,
25784
+ * it's probably best to subscribe the "dispose" event.
25785
+ *
25786
+ * @fires Plugin#dispose
25787
+ */
25788
+
25789
+
25790
+ Plugin.prototype.dispose = function dispose() {
25791
+ var name = this.name,
25792
+ player = this.player;
25793
+
25794
+ /**
25795
+ * Signals that a advanced plugin is about to be disposed.
25796
+ *
25797
+ * @event Plugin#dispose
25798
+ * @type {EventTarget~Event}
25799
+ */
25800
+
25801
+ this.trigger('dispose');
25802
+ this.off();
25803
+ player.off('dispose', this.dispose);
25804
+
25805
+ // Eliminate any possible sources of leaking memory by clearing up
25806
+ // references between the player and the plugin instance and nulling out
25807
+ // the plugin's state and replacing methods with a function that throws.
25808
+ player[PLUGIN_CACHE_KEY][name] = false;
25809
+ this.player = this.state = null;
25810
+
25811
+ // Finally, replace the plugin name on the player with a new factory
25812
+ // function, so that the plugin is ready to be set up again.
25813
+ player[name] = createPluginFactory(name, pluginStorage[name]);
25814
+ };
25815
+
25816
+ /**
25817
+ * Determines if a plugin is a basic plugin (i.e. not a sub-class of `Plugin`).
25818
+ *
25819
+ * @param {string|Function} plugin
25820
+ * If a string, matches the name of a plugin. If a function, will be
25821
+ * tested directly.
25822
+ *
25823
+ * @returns {boolean}
25824
+ * Whether or not a plugin is a basic plugin.
25825
+ */
25826
+
25827
+
25828
+ Plugin.isBasic = function isBasic(plugin) {
25829
+ var p = typeof plugin === 'string' ? getPlugin(plugin) : plugin;
25830
+
25831
+ return typeof p === 'function' && !Plugin.prototype.isPrototypeOf(p.prototype);
25832
+ };
25833
+
25834
+ /**
25835
+ * Register a Video.js plugin.
25836
+ *
25837
+ * @param {string} name
25838
+ * The name of the plugin to be registered. Must be a string and
25839
+ * must not match an existing plugin or a method on the `Player`
25840
+ * prototype.
25841
+ *
25842
+ * @param {Function} plugin
25843
+ * A sub-class of `Plugin` or a function for basic plugins.
25844
+ *
25845
+ * @returns {Function}
25846
+ * For advanced plugins, a factory function for that plugin. For
25847
+ * basic plugins, a wrapper function that initializes the plugin.
25848
+ */
25849
+
25850
+
25851
+ Plugin.registerPlugin = function registerPlugin(name, plugin) {
25852
+ if (typeof name !== 'string') {
25853
+ throw new Error('Illegal plugin name, "' + name + '", must be a string, was ' + (typeof name === 'undefined' ? 'undefined' : _typeof(name)) + '.');
25854
+ }
25855
+
25856
+ if (pluginExists(name)) {
25857
+ log$1.warn('A plugin named "' + name + '" already exists. You may want to avoid re-registering plugins!');
25858
+ } else if (Player.prototype.hasOwnProperty(name)) {
25859
+ throw new Error('Illegal plugin name, "' + name + '", cannot share a name with an existing player method!');
25860
+ }
25861
+
25862
+ if (typeof plugin !== 'function') {
25863
+ throw new Error('Illegal plugin for "' + name + '", must be a function, was ' + (typeof plugin === 'undefined' ? 'undefined' : _typeof(plugin)) + '.');
25864
+ }
25865
+
25866
+ pluginStorage[name] = plugin;
25867
+
25868
+ // Add a player prototype method for all sub-classed plugins (but not for
25869
+ // the base Plugin class).
25870
+ if (name !== BASE_PLUGIN_NAME) {
25871
+ if (Plugin.isBasic(plugin)) {
25872
+ Player.prototype[name] = createBasicPlugin(name, plugin);
25873
+ } else {
25874
+ Player.prototype[name] = createPluginFactory(name, plugin);
25875
+ }
25876
+ }
25877
+
25878
+ return plugin;
25879
+ };
25880
+
25881
+ /**
25882
+ * De-register a Video.js plugin.
25883
+ *
25884
+ * @param {string} name
25885
+ * The name of the plugin to be deregistered.
25886
+ */
25887
+
25888
+
25889
+ Plugin.deregisterPlugin = function deregisterPlugin(name) {
25890
+ if (name === BASE_PLUGIN_NAME) {
25891
+ throw new Error('Cannot de-register base plugin.');
25892
+ }
25893
+ if (pluginExists(name)) {
25894
+ delete pluginStorage[name];
25895
+ delete Player.prototype[name];
25896
+ }
25897
+ };
25898
+
25899
+ /**
25900
+ * Gets an object containing multiple Video.js plugins.
25901
+ *
25902
+ * @param {Array} [names]
25903
+ * If provided, should be an array of plugin names. Defaults to _all_
25904
+ * plugin names.
25905
+ *
25906
+ * @returns {Object|undefined}
25907
+ * An object containing plugin(s) associated with their name(s) or
25908
+ * `undefined` if no matching plugins exist).
25909
+ */
25910
+
25911
+
25912
+ Plugin.getPlugins = function getPlugins() {
25913
+ var names = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : Object.keys(pluginStorage);
25914
+
25915
+ var result = void 0;
25916
+
25917
+ names.forEach(function (name) {
25918
+ var plugin = getPlugin(name);
25919
+
25920
+ if (plugin) {
25921
+ result = result || {};
25922
+ result[name] = plugin;
25923
+ }
25924
+ });
25925
+
25926
+ return result;
25927
+ };
25928
+
25929
+ /**
25930
+ * Gets a plugin's version, if available
25931
+ *
25932
+ * @param {string} name
25933
+ * The name of a plugin.
25934
+ *
25935
+ * @returns {string}
25936
+ * The plugin's version or an empty string.
25937
+ */
25938
+
25939
+
25940
+ Plugin.getPluginVersion = function getPluginVersion(name) {
25941
+ var plugin = getPlugin(name);
25942
+
25943
+ return plugin && plugin.VERSION || '';
25944
+ };
25945
+
25946
+ return Plugin;
25947
+ }();
25948
+
25949
+ /**
25950
+ * Gets a plugin by name if it exists.
25951
+ *
25952
+ * @static
25953
+ * @method getPlugin
25954
+ * @memberOf Plugin
25955
+ * @param {string} name
25956
+ * The name of a plugin.
25957
+ *
25958
+ * @returns {Function|undefined}
25959
+ * The plugin (or `undefined`).
25960
+ */
25961
+
25962
+
25963
+ Plugin.getPlugin = getPlugin;
25964
+
25965
+ /**
25966
+ * The name of the base plugin class as it is registered.
25967
+ *
25968
+ * @type {string}
25969
+ */
25970
+ Plugin.BASE_PLUGIN_NAME = BASE_PLUGIN_NAME;
25971
+
25972
+ Plugin.registerPlugin(BASE_PLUGIN_NAME, Plugin);
25973
+
25974
+ /**
25975
+ * Documented in player.js
25976
+ *
25977
+ * @ignore
25978
+ */
25979
+ Player.prototype.usingPlugin = function (name) {
25980
+ return !!this[PLUGIN_CACHE_KEY] && this[PLUGIN_CACHE_KEY][name] === true;
25981
+ };
25982
+
25983
+ /**
25984
+ * Documented in player.js
25985
+ *
25986
+ * @ignore
25987
+ */
25988
+ Player.prototype.hasPlugin = function (name) {
25989
+ return !!pluginExists(name);
25990
+ };
25991
+
25992
+ /**
25993
+ * @file extend.js
25994
+ * @module extend
25995
+ */
25996
+
25997
+ /**
25998
+ * A combination of node inherits and babel's inherits (after transpile).
25999
+ * Both work the same but node adds `super_` to the subClass
26000
+ * and Bable adds the superClass as __proto__. Both seem useful.
26001
+ *
26002
+ * @param {Object} subClass
26003
+ * The class to inherit to
26004
+ *
26005
+ * @param {Object} superClass
26006
+ * The class to inherit from
26007
+ *
26008
+ * @private
26009
+ */
26010
+ var _inherits = function _inherits(subClass, superClass) {
26011
+ if (typeof superClass !== 'function' && superClass !== null) {
26012
+ throw new TypeError('Super expression must either be null or a function, not ' + (typeof superClass === 'undefined' ? 'undefined' : _typeof(superClass)));
26013
+ }
26014
+
26015
+ subClass.prototype = Object.create(superClass && superClass.prototype, {
26016
+ constructor: {
26017
+ value: subClass,
26018
+ enumerable: false,
26019
+ writable: true,
26020
+ configurable: true
26021
+ }
26022
+ });
26023
+
26024
+ if (superClass) {
26025
+ // node
26026
+ subClass.super_ = superClass;
26027
+ }
26028
+ };
26029
+
26030
+ /**
26031
+ * Function for subclassing using the same inheritance that
26032
+ * videojs uses internally
26033
+ *
26034
+ * @static
26035
+ * @const
26036
+ *
26037
+ * @param {Object} superClass
26038
+ * The class to inherit from
26039
+ *
26040
+ * @param {Object} [subClassMethods={}]
26041
+ * The class to inherit to
26042
+ *
26043
+ * @return {Object}
26044
+ * The new object with subClassMethods that inherited superClass.
26045
+ */
26046
+ var extendFn = function extendFn(superClass) {
26047
+ var subClassMethods = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
26048
+
26049
+ var subClass = function subClass() {
26050
+ superClass.apply(this, arguments);
26051
+ };
26052
+
26053
+ var methods = {};
26054
+
26055
+ if ((typeof subClassMethods === 'undefined' ? 'undefined' : _typeof(subClassMethods)) === 'object') {
26056
+ if (subClassMethods.constructor !== Object.prototype.constructor) {
26057
+ subClass = subClassMethods.constructor;
26058
+ }
26059
+ methods = subClassMethods;
26060
+ } else if (typeof subClassMethods === 'function') {
26061
+ subClass = subClassMethods;
26062
+ }
26063
+
26064
+ _inherits(subClass, superClass);
26065
+
26066
+ // Extend subObj's prototype with functions and other properties from props
26067
+ for (var name in methods) {
26068
+ if (methods.hasOwnProperty(name)) {
26069
+ subClass.prototype[name] = methods[name];
26070
+ }
26071
+ }
26072
+
26073
+ return subClass;
26074
+ };
26075
+
26076
+ /**
26077
+ * @file video.js
26078
+ * @module videojs
26079
+ */
26080
+
26081
+ /**
26082
+ * Normalize an `id` value by trimming off a leading `#`
26083
+ *
26084
+ * @param {string} id
26085
+ * A string, maybe with a leading `#`.
26086
+ *
26087
+ * @returns {string}
26088
+ * The string, without any leading `#`.
26089
+ */
26090
+ var normalizeId = function normalizeId(id) {
26091
+ return id.indexOf('#') === 0 ? id.slice(1) : id;
26092
+ };
26093
+
26094
+ /**
26095
+ * Doubles as the main function for users to create a player instance and also
26096
+ * the main library object.
26097
+ * The `videojs` function can be used to initialize or retrieve a player.
26098
+ *
26099
+ * @param {string|Element} id
26100
+ * Video element or video element ID
26101
+ *
26102
+ * @param {Object} [options]
26103
+ * Optional options object for config/settings
26104
+ *
26105
+ * @param {Component~ReadyCallback} [ready]
26106
+ * Optional ready callback
26107
+ *
26108
+ * @return {Player}
26109
+ * A player instance
26110
+ */
26111
+ function videojs$1(id, options, ready) {
26112
+ var player = videojs$1.getPlayer(id);
26113
+
26114
+ if (player) {
26115
+ if (options) {
26116
+ log$1.warn('Player "' + id + '" is already initialised. Options will not be applied.');
26117
+ }
26118
+ if (ready) {
26119
+ player.ready(ready);
26120
+ }
26121
+ return player;
26122
+ }
26123
+
26124
+ var el = typeof id === 'string' ? $('#' + normalizeId(id)) : id;
26125
+
26126
+ if (!isEl(el)) {
26127
+ throw new TypeError('The element or ID supplied is not valid. (videojs)');
26128
+ }
26129
+
26130
+ if (!document_1.body.contains(el)) {
26131
+ log$1.warn('The element supplied is not included in the DOM');
26132
+ }
26133
+
26134
+ options = options || {};
26135
+
26136
+ videojs$1.hooks('beforesetup').forEach(function (hookFunction) {
26137
+ var opts = hookFunction(el, mergeOptions(options));
26138
+
26139
+ if (!isObject(opts) || Array.isArray(opts)) {
26140
+ log$1.error('please return an object in beforesetup hooks');
26141
+ return;
26142
+ }
26143
+
26144
+ options = mergeOptions(options, opts);
26145
+ });
26146
+
26147
+ // We get the current "Player" component here in case an integration has
26148
+ // replaced it with a custom player.
26149
+ var PlayerComponent = Component.getComponent('Player');
26150
+
26151
+ player = new PlayerComponent(el, options, ready);
26152
+
26153
+ videojs$1.hooks('setup').forEach(function (hookFunction) {
26154
+ return hookFunction(player);
26155
+ });
26156
+
26157
+ return player;
26158
+ }
26159
+
26160
+ /**
26161
+ * An Object that contains lifecycle hooks as keys which point to an array
26162
+ * of functions that are run when a lifecycle is triggered
26163
+ */
26164
+ videojs$1.hooks_ = {};
26165
+
26166
+ /**
26167
+ * Get a list of hooks for a specific lifecycle
26168
+ * @function videojs.hooks
26169
+ *
26170
+ * @param {string} type
26171
+ * the lifecyle to get hooks from
26172
+ *
26173
+ * @param {Function|Function[]} [fn]
26174
+ * Optionally add a hook (or hooks) to the lifecycle that your are getting.
26175
+ *
26176
+ * @return {Array}
26177
+ * an array of hooks, or an empty array if there are none.
26178
+ */
26179
+ videojs$1.hooks = function (type, fn) {
26180
+ videojs$1.hooks_[type] = videojs$1.hooks_[type] || [];
26181
+ if (fn) {
26182
+ videojs$1.hooks_[type] = videojs$1.hooks_[type].concat(fn);
26183
+ }
26184
+ return videojs$1.hooks_[type];
26185
+ };
26186
+
26187
+ /**
26188
+ * Add a function hook to a specific videojs lifecycle.
26189
+ *
26190
+ * @param {string} type
26191
+ * the lifecycle to hook the function to.
26192
+ *
26193
+ * @param {Function|Function[]}
26194
+ * The function or array of functions to attach.
26195
+ */
26196
+ videojs$1.hook = function (type, fn) {
26197
+ videojs$1.hooks(type, fn);
26198
+ };
26199
+
26200
+ /**
26201
+ * Add a function hook that will only run once to a specific videojs lifecycle.
26202
+ *
26203
+ * @param {string} type
26204
+ * the lifecycle to hook the function to.
26205
+ *
26206
+ * @param {Function|Function[]}
26207
+ * The function or array of functions to attach.
26208
+ */
26209
+ videojs$1.hookOnce = function (type, fn) {
26210
+ videojs$1.hooks(type, [].concat(fn).map(function (original) {
26211
+ var wrapper = function wrapper() {
26212
+ videojs$1.removeHook(type, wrapper);
26213
+ return original.apply(undefined, arguments);
26214
+ };
26215
+
26216
+ return wrapper;
26217
+ }));
26218
+ };
26219
+
26220
+ /**
26221
+ * Remove a hook from a specific videojs lifecycle.
26222
+ *
26223
+ * @param {string} type
26224
+ * the lifecycle that the function hooked to
26225
+ *
26226
+ * @param {Function} fn
26227
+ * The hooked function to remove
26228
+ *
26229
+ * @return {boolean}
26230
+ * The function that was removed or undef
26231
+ */
26232
+ videojs$1.removeHook = function (type, fn) {
26233
+ var index = videojs$1.hooks(type).indexOf(fn);
26234
+
26235
+ if (index <= -1) {
26236
+ return false;
26237
+ }
26238
+
26239
+ videojs$1.hooks_[type] = videojs$1.hooks_[type].slice();
26240
+ videojs$1.hooks_[type].splice(index, 1);
26241
+
26242
+ return true;
26243
+ };
26244
+
26245
+ // Add default styles
26246
+ if (window_1.VIDEOJS_NO_DYNAMIC_STYLE !== true && isReal()) {
26247
+ var style$1 = $('.vjs-styles-defaults');
26248
+
26249
+ if (!style$1) {
26250
+ style$1 = createStyleElement('vjs-styles-defaults');
26251
+ var head = $('head');
26252
+
26253
+ if (head) {
26254
+ head.insertBefore(style$1, head.firstChild);
26255
+ }
26256
+ setTextContent(style$1, '\n .video-js {\n width: 300px;\n height: 150px;\n }\n\n .vjs-fluid {\n padding-top: 56.25%\n }\n ');
26257
+ }
26258
+ }
26259
+
26260
+ // Run Auto-load players
26261
+ // You have to wait at least once in case this script is loaded after your
26262
+ // video in the DOM (weird behavior only with minified version)
26263
+ autoSetupTimeout(1, videojs$1);
26264
+
26265
+ /**
26266
+ * Current software version. Follows semver.
26267
+ *
26268
+ * @type {string}
26269
+ */
26270
+ videojs$1.VERSION = version;
26271
+
26272
+ /**
26273
+ * The global options object. These are the settings that take effect
26274
+ * if no overrides are specified when the player is created.
26275
+ *
26276
+ * @type {Object}
26277
+ */
26278
+ videojs$1.options = Player.prototype.options_;
26279
+
26280
+ /**
26281
+ * Get an object with the currently created players, keyed by player ID
26282
+ *
26283
+ * @return {Object}
26284
+ * The created players
26285
+ */
26286
+ videojs$1.getPlayers = function () {
26287
+ return Player.players;
26288
+ };
26289
+
26290
+ /**
26291
+ * Get a single player based on an ID or DOM element.
26292
+ *
26293
+ * This is useful if you want to check if an element or ID has an associated
26294
+ * Video.js player, but not create one if it doesn't.
26295
+ *
26296
+ * @param {string|Element} id
26297
+ * An HTML element - `<video>`, `<audio>`, or `<video-js>` -
26298
+ * or a string matching the `id` of such an element.
26299
+ *
26300
+ * @returns {Player|undefined}
26301
+ * A player instance or `undefined` if there is no player instance
26302
+ * matching the argument.
26303
+ */
26304
+ videojs$1.getPlayer = function (id) {
26305
+ var players = Player.players;
26306
+ var tag = void 0;
26307
+
26308
+ if (typeof id === 'string') {
26309
+ var nId = normalizeId(id);
26310
+ var player = players[nId];
26311
+
26312
+ if (player) {
26313
+ return player;
26314
+ }
26315
+
26316
+ tag = $('#' + nId);
26317
+ } else {
26318
+ tag = id;
26319
+ }
26320
+
26321
+ if (isEl(tag)) {
26322
+ var _tag = tag,
26323
+ _player = _tag.player,
26324
+ playerId = _tag.playerId;
26325
+
26326
+ // Element may have a `player` property referring to an already created
26327
+ // player instance. If so, return that.
26328
+
26329
+ if (_player || players[playerId]) {
26330
+ return _player || players[playerId];
26331
+ }
26332
+ }
26333
+ };
26334
+
26335
+ /**
26336
+ * Returns an array of all current players.
26337
+ *
26338
+ * @return {Array}
26339
+ * An array of all players. The array will be in the order that
26340
+ * `Object.keys` provides, which could potentially vary between
26341
+ * JavaScript engines.
26342
+ *
26343
+ */
26344
+ videojs$1.getAllPlayers = function () {
26345
+ return (
26346
+
26347
+ // Disposed players leave a key with a `null` value, so we need to make sure
26348
+ // we filter those out.
26349
+ Object.keys(Player.players).map(function (k) {
26350
+ return Player.players[k];
26351
+ }).filter(Boolean)
26352
+ );
26353
+ };
26354
+
26355
+ /**
26356
+ * Expose players object.
26357
+ *
26358
+ * @memberOf videojs
26359
+ * @property {Object} players
26360
+ */
26361
+ videojs$1.players = Player.players;
26362
+
26363
+ /**
26364
+ * Get a component class object by name
26365
+ *
26366
+ * @borrows Component.getComponent as videojs.getComponent
26367
+ */
26368
+ videojs$1.getComponent = Component.getComponent;
26369
+
26370
+ /**
26371
+ * Register a component so it can referred to by name. Used when adding to other
26372
+ * components, either through addChild `component.addChild('myComponent')` or through
26373
+ * default children options `{ children: ['myComponent'] }`.
26374
+ *
26375
+ * > NOTE: You could also just initialize the component before adding.
26376
+ * `component.addChild(new MyComponent());`
26377
+ *
26378
+ * @param {string} name
26379
+ * The class name of the component
26380
+ *
26381
+ * @param {Component} comp
26382
+ * The component class
26383
+ *
26384
+ * @return {Component}
26385
+ * The newly registered component
26386
+ */
26387
+ videojs$1.registerComponent = function (name$$1, comp) {
26388
+ if (Tech.isTech(comp)) {
26389
+ log$1.warn('The ' + name$$1 + ' tech was registered as a component. It should instead be registered using videojs.registerTech(name, tech)');
26390
+ }
26391
+
26392
+ Component.registerComponent.call(Component, name$$1, comp);
26393
+ };
26394
+
26395
+ /**
26396
+ * Get a Tech class object by name
26397
+ *
26398
+ * @borrows Tech.getTech as videojs.getTech
26399
+ */
26400
+ videojs$1.getTech = Tech.getTech;
26401
+
26402
+ /**
26403
+ * Register a Tech so it can referred to by name.
26404
+ * This is used in the tech order for the player.
26405
+ *
26406
+ * @borrows Tech.registerTech as videojs.registerTech
26407
+ */
26408
+ videojs$1.registerTech = Tech.registerTech;
26409
+
26410
+ /**
26411
+ * Register a middleware to a source type.
26412
+ *
26413
+ * @param {String} type A string representing a MIME type.
26414
+ * @param {function(player):object} middleware A middleware factory that takes a player.
26415
+ */
26416
+ videojs$1.use = use;
26417
+
26418
+ /**
26419
+ * An object that can be returned by a middleware to signify
26420
+ * that the middleware is being terminated.
26421
+ *
26422
+ * @type {object}
26423
+ * @memberOf {videojs}
26424
+ * @property {object} middleware.TERMINATOR
26425
+ */
26426
+ Object.defineProperty(videojs$1, 'middleware', {
26427
+ value: {},
26428
+ writeable: false,
26429
+ enumerable: true
26430
+ });
26431
+
26432
+ Object.defineProperty(videojs$1.middleware, 'TERMINATOR', {
26433
+ value: TERMINATOR,
26434
+ writeable: false,
26435
+ enumerable: true
26436
+ });
26437
+
26438
+ /**
26439
+ * A suite of browser and device tests from {@link browser}.
26440
+ *
26441
+ * @type {Object}
26442
+ * @private
26443
+ */
26444
+ videojs$1.browser = browser;
26445
+
26446
+ /**
26447
+ * Whether or not the browser supports touch events. Included for backward
26448
+ * compatibility with 4.x, but deprecated. Use `videojs.browser.TOUCH_ENABLED`
26449
+ * instead going forward.
26450
+ *
26451
+ * @deprecated since version 5.0
26452
+ * @type {boolean}
26453
+ */
26454
+ videojs$1.TOUCH_ENABLED = TOUCH_ENABLED;
26455
+
26456
+ /**
26457
+ * Subclass an existing class
26458
+ * Mimics ES6 subclassing with the `extend` keyword
26459
+ *
26460
+ * @borrows extend:extendFn as videojs.extend
26461
+ */
26462
+ videojs$1.extend = extendFn;
26463
+
26464
+ /**
26465
+ * Merge two options objects recursively
26466
+ * Performs a deep merge like lodash.merge but **only merges plain objects**
26467
+ * (not arrays, elements, anything else)
26468
+ * Other values will be copied directly from the second object.
26469
+ *
26470
+ * @borrows merge-options:mergeOptions as videojs.mergeOptions
26471
+ */
26472
+ videojs$1.mergeOptions = mergeOptions;
26473
+
26474
+ /**
26475
+ * Change the context (this) of a function
26476
+ *
26477
+ * > NOTE: as of v5.0 we require an ES5 shim, so you should use the native
26478
+ * `function() {}.bind(newContext);` instead of this.
26479
+ *
26480
+ * @borrows fn:bind as videojs.bind
26481
+ */
26482
+ videojs$1.bind = bind;
26483
+
26484
+ /**
26485
+ * Register a Video.js plugin.
26486
+ *
26487
+ * @borrows plugin:registerPlugin as videojs.registerPlugin
26488
+ * @method registerPlugin
26489
+ *
26490
+ * @param {string} name
26491
+ * The name of the plugin to be registered. Must be a string and
26492
+ * must not match an existing plugin or a method on the `Player`
26493
+ * prototype.
26494
+ *
26495
+ * @param {Function} plugin
26496
+ * A sub-class of `Plugin` or a function for basic plugins.
26497
+ *
26498
+ * @return {Function}
26499
+ * For advanced plugins, a factory function for that plugin. For
26500
+ * basic plugins, a wrapper function that initializes the plugin.
26501
+ */
26502
+ videojs$1.registerPlugin = Plugin.registerPlugin;
26503
+
26504
+ /**
26505
+ * Deprecated method to register a plugin with Video.js
26506
+ *
26507
+ * @deprecated
26508
+ * videojs.plugin() is deprecated; use videojs.registerPlugin() instead
26509
+ *
26510
+ * @param {string} name
26511
+ * The plugin name
26512
+ *
26513
+ * @param {Plugin|Function} plugin
26514
+ * The plugin sub-class or function
26515
+ */
26516
+ videojs$1.plugin = function (name$$1, plugin) {
26517
+ log$1.warn('videojs.plugin() is deprecated; use videojs.registerPlugin() instead');
26518
+ return Plugin.registerPlugin(name$$1, plugin);
26519
+ };
26520
+
26521
+ /**
26522
+ * Gets an object containing multiple Video.js plugins.
26523
+ *
26524
+ * @param {Array} [names]
26525
+ * If provided, should be an array of plugin names. Defaults to _all_
26526
+ * plugin names.
26527
+ *
26528
+ * @return {Object|undefined}
26529
+ * An object containing plugin(s) associated with their name(s) or
26530
+ * `undefined` if no matching plugins exist).
26531
+ */
26532
+ videojs$1.getPlugins = Plugin.getPlugins;
26533
+
26534
+ /**
26535
+ * Gets a plugin by name if it exists.
26536
+ *
26537
+ * @param {string} name
26538
+ * The name of a plugin.
26539
+ *
26540
+ * @return {Function|undefined}
26541
+ * The plugin (or `undefined`).
26542
+ */
26543
+ videojs$1.getPlugin = Plugin.getPlugin;
26544
+
26545
+ /**
26546
+ * Gets a plugin's version, if available
26547
+ *
26548
+ * @param {string} name
26549
+ * The name of a plugin.
26550
+ *
26551
+ * @return {string}
26552
+ * The plugin's version or an empty string.
26553
+ */
26554
+ videojs$1.getPluginVersion = Plugin.getPluginVersion;
26555
+
26556
+ /**
26557
+ * Adding languages so that they're available to all players.
26558
+ * Example: `videojs.addLanguage('es', { 'Hello': 'Hola' });`
26559
+ *
26560
+ * @param {string} code
26561
+ * The language code or dictionary property
26562
+ *
26563
+ * @param {Object} data
26564
+ * The data values to be translated
26565
+ *
26566
+ * @return {Object}
26567
+ * The resulting language dictionary object
26568
+ */
26569
+ videojs$1.addLanguage = function (code, data) {
26570
+ var _mergeOptions;
26571
+
26572
+ code = ('' + code).toLowerCase();
26573
+
26574
+ videojs$1.options.languages = mergeOptions(videojs$1.options.languages, (_mergeOptions = {}, _mergeOptions[code] = data, _mergeOptions));
26575
+
26576
+ return videojs$1.options.languages[code];
26577
+ };
26578
+
26579
+ /**
26580
+ * Log messages
26581
+ *
26582
+ * @borrows log:log as videojs.log
26583
+ */
26584
+ videojs$1.log = log$1;
26585
+
26586
+ /**
26587
+ * Creates an emulated TimeRange object.
26588
+ *
26589
+ * @borrows time-ranges:createTimeRanges as videojs.createTimeRange
26590
+ */
26591
+ /**
26592
+ * @borrows time-ranges:createTimeRanges as videojs.createTimeRanges
26593
+ */
26594
+ videojs$1.createTimeRange = videojs$1.createTimeRanges = createTimeRanges;
26595
+
26596
+ /**
26597
+ * Format seconds as a time string, H:MM:SS or M:SS
26598
+ * Supplying a guide (in seconds) will force a number of leading zeros
26599
+ * to cover the length of the guide
26600
+ *
26601
+ * @borrows format-time:formatTime as videojs.formatTime
26602
+ */
26603
+ videojs$1.formatTime = formatTime;
26604
+
26605
+ /**
26606
+ * Replaces format-time with a custom implementation, to be used in place of the default.
26607
+ *
26608
+ * @borrows format-time:setFormatTime as videojs.setFormatTime
26609
+ *
26610
+ * @method setFormatTime
26611
+ *
26612
+ * @param {Function} customFn
26613
+ * A custom format-time function which will be called with the current time and guide (in seconds) as arguments.
26614
+ * Passed fn should return a string.
26615
+ */
26616
+ videojs$1.setFormatTime = setFormatTime;
26617
+
26618
+ /**
26619
+ * Resets format-time to the default implementation.
26620
+ *
26621
+ * @borrows format-time:resetFormatTime as videojs.resetFormatTime
26622
+ *
26623
+ * @method resetFormatTime
26624
+ */
26625
+ videojs$1.resetFormatTime = resetFormatTime;
26626
+
26627
+ /**
26628
+ * Resolve and parse the elements of a URL
26629
+ *
26630
+ * @borrows url:parseUrl as videojs.parseUrl
26631
+ *
26632
+ */
26633
+ videojs$1.parseUrl = parseUrl;
26634
+
26635
+ /**
26636
+ * Returns whether the url passed is a cross domain request or not.
26637
+ *
26638
+ * @borrows url:isCrossOrigin as videojs.isCrossOrigin
26639
+ */
26640
+ videojs$1.isCrossOrigin = isCrossOrigin;
26641
+
26642
+ /**
26643
+ * Event target class.
26644
+ *
26645
+ * @borrows EventTarget as videojs.EventTarget
26646
+ */
26647
+ videojs$1.EventTarget = EventTarget;
26648
+
26649
+ /**
26650
+ * Add an event listener to element
26651
+ * It stores the handler function in a separate cache object
26652
+ * and adds a generic handler to the element's event,
26653
+ * along with a unique id (guid) to the element.
26654
+ *
26655
+ * @borrows events:on as videojs.on
26656
+ */
26657
+ videojs$1.on = on;
26658
+
26659
+ /**
26660
+ * Trigger a listener only once for an event
26661
+ *
26662
+ * @borrows events:one as videojs.one
26663
+ */
26664
+ videojs$1.one = one;
26665
+
26666
+ /**
26667
+ * Removes event listeners from an element
26668
+ *
26669
+ * @borrows events:off as videojs.off
26670
+ */
26671
+ videojs$1.off = off;
26672
+
26673
+ /**
26674
+ * Trigger an event for an element
26675
+ *
26676
+ * @borrows events:trigger as videojs.trigger
26677
+ */
26678
+ videojs$1.trigger = trigger;
26679
+
26680
+ /**
26681
+ * A cross-browser XMLHttpRequest wrapper. Here's a simple example:
26682
+ *
26683
+ * @param {Object} options
26684
+ * settings for the request.
26685
+ *
26686
+ * @return {XMLHttpRequest|XDomainRequest}
26687
+ * The request object.
26688
+ *
26689
+ * @see https://github.com/Raynos/xhr
26690
+ */
26691
+ videojs$1.xhr = xhr;
26692
+
26693
+ /**
26694
+ * TextTrack class
26695
+ *
26696
+ * @borrows TextTrack as videojs.TextTrack
26697
+ */
26698
+ videojs$1.TextTrack = TextTrack;
26699
+
26700
+ /**
26701
+ * export the AudioTrack class so that source handlers can create
26702
+ * AudioTracks and then add them to the players AudioTrackList
26703
+ *
26704
+ * @borrows AudioTrack as videojs.AudioTrack
26705
+ */
26706
+ videojs$1.AudioTrack = AudioTrack;
26707
+
26708
+ /**
26709
+ * export the VideoTrack class so that source handlers can create
26710
+ * VideoTracks and then add them to the players VideoTrackList
26711
+ *
26712
+ * @borrows VideoTrack as videojs.VideoTrack
26713
+ */
26714
+ videojs$1.VideoTrack = VideoTrack;
26715
+
26716
+ /**
26717
+ * Determines, via duck typing, whether or not a value is a DOM element.
26718
+ *
26719
+ * @borrows dom:isEl as videojs.isEl
26720
+ * @deprecated Use videojs.dom.isEl() instead
26721
+ */
26722
+
26723
+ /**
26724
+ * Determines, via duck typing, whether or not a value is a text node.
26725
+ *
26726
+ * @borrows dom:isTextNode as videojs.isTextNode
26727
+ * @deprecated Use videojs.dom.isTextNode() instead
26728
+ */
26729
+
26730
+ /**
26731
+ * Creates an element and applies properties.
26732
+ *
26733
+ * @borrows dom:createEl as videojs.createEl
26734
+ * @deprecated Use videojs.dom.createEl() instead
26735
+ */
26736
+
26737
+ /**
26738
+ * Check if an element has a CSS class
26739
+ *
26740
+ * @borrows dom:hasElClass as videojs.hasClass
26741
+ * @deprecated Use videojs.dom.hasClass() instead
26742
+ */
26743
+
26744
+ /**
26745
+ * Add a CSS class name to an element
26746
+ *
26747
+ * @borrows dom:addElClass as videojs.addClass
26748
+ * @deprecated Use videojs.dom.addClass() instead
26749
+ */
26750
+
26751
+ /**
26752
+ * Remove a CSS class name from an element
26753
+ *
26754
+ * @borrows dom:removeElClass as videojs.removeClass
26755
+ * @deprecated Use videojs.dom.removeClass() instead
26756
+ */
26757
+
26758
+ /**
26759
+ * Adds or removes a CSS class name on an element depending on an optional
26760
+ * condition or the presence/absence of the class name.
26761
+ *
26762
+ * @borrows dom:toggleElClass as videojs.toggleClass
26763
+ * @deprecated Use videojs.dom.toggleClass() instead
26764
+ */
26765
+
26766
+ /**
26767
+ * Apply attributes to an HTML element.
26768
+ *
26769
+ * @borrows dom:setElAttributes as videojs.setAttribute
26770
+ * @deprecated Use videojs.dom.setAttributes() instead
26771
+ */
26772
+
26773
+ /**
26774
+ * Get an element's attribute values, as defined on the HTML tag
26775
+ * Attributes are not the same as properties. They're defined on the tag
26776
+ * or with setAttribute (which shouldn't be used with HTML)
26777
+ * This will return true or false for boolean attributes.
26778
+ *
26779
+ * @borrows dom:getElAttributes as videojs.getAttributes
26780
+ * @deprecated Use videojs.dom.getAttributes() instead
26781
+ */
26782
+
26783
+ /**
26784
+ * Empties the contents of an element.
26785
+ *
26786
+ * @borrows dom:emptyEl as videojs.emptyEl
26787
+ * @deprecated Use videojs.dom.emptyEl() instead
26788
+ */
26789
+
26790
+ /**
26791
+ * Normalizes and appends content to an element.
26792
+ *
26793
+ * The content for an element can be passed in multiple types and
26794
+ * combinations, whose behavior is as follows:
26795
+ *
26796
+ * - String
26797
+ * Normalized into a text node.
26798
+ *
26799
+ * - Element, TextNode
26800
+ * Passed through.
26801
+ *
26802
+ * - Array
26803
+ * A one-dimensional array of strings, elements, nodes, or functions (which
26804
+ * return single strings, elements, or nodes).
26805
+ *
26806
+ * - Function
26807
+ * If the sole argument, is expected to produce a string, element,
26808
+ * node, or array.
26809
+ *
26810
+ * @borrows dom:appendContents as videojs.appendContet
26811
+ * @deprecated Use videojs.dom.appendContent() instead
26812
+ */
26813
+
26814
+ /**
26815
+ * Normalizes and inserts content into an element; this is identical to
26816
+ * `appendContent()`, except it empties the element first.
26817
+ *
26818
+ * The content for an element can be passed in multiple types and
26819
+ * combinations, whose behavior is as follows:
26820
+ *
26821
+ * - String
26822
+ * Normalized into a text node.
26823
+ *
26824
+ * - Element, TextNode
26825
+ * Passed through.
26826
+ *
26827
+ * - Array
26828
+ * A one-dimensional array of strings, elements, nodes, or functions (which
26829
+ * return single strings, elements, or nodes).
26830
+ *
26831
+ * - Function
26832
+ * If the sole argument, is expected to produce a string, element,
26833
+ * node, or array.
26834
+ *
26835
+ * @borrows dom:insertContent as videojs.insertContent
26836
+ * @deprecated Use videojs.dom.insertContent() instead
26837
+ */
26838
+ ['isEl', 'isTextNode', 'createEl', 'hasClass', 'addClass', 'removeClass', 'toggleClass', 'setAttributes', 'getAttributes', 'emptyEl', 'appendContent', 'insertContent'].forEach(function (k) {
26839
+ videojs$1[k] = function () {
26840
+ log$1.warn('videojs.' + k + '() is deprecated; use videojs.dom.' + k + '() instead');
26841
+ return Dom[k].apply(null, arguments);
26842
+ };
26843
+ });
26844
+
26845
+ /**
26846
+ * A safe getComputedStyle.
26847
+ *
26848
+ * This is because in Firefox, if the player is loaded in an iframe with `display:none`,
26849
+ * then `getComputedStyle` returns `null`, so, we do a null-check to make sure
26850
+ * that the player doesn't break in these cases.
26851
+ * See https://bugzilla.mozilla.org/show_bug.cgi?id=548397 for more details.
26852
+ *
26853
+ * @borrows computed-style:computedStyle as videojs.computedStyle
26854
+ */
26855
+ videojs$1.computedStyle = computedStyle;
26856
+
26857
+ /**
26858
+ * Export the Dom utilities for use in external plugins
26859
+ * and Tech's
26860
+ */
26861
+ videojs$1.dom = Dom;
26862
+
26863
+ /**
26864
+ * Export the Url utilities for use in external plugins
26865
+ * and Tech's
26866
+ */
26867
+ videojs$1.url = Url;
26868
+
26869
+ var urlToolkit = createCommonjsModule(function (module, exports) {
26870
+ // see https://tools.ietf.org/html/rfc1808
26871
+
26872
+ /* jshint ignore:start */
26873
+ (function (root) {
26874
+ /* jshint ignore:end */
26875
+
26876
+ var URL_REGEX = /^((?:[a-zA-Z0-9+\-.]+:)?)(\/\/[^\/\;?#]*)?(.*?)??(;.*?)?(\?.*?)?(#.*?)?$/;
26877
+ var FIRST_SEGMENT_REGEX = /^([^\/;?#]*)(.*)$/;
26878
+ var SLASH_DOT_REGEX = /(?:\/|^)\.(?=\/)/g;
26879
+ var SLASH_DOT_DOT_REGEX = /(?:\/|^)\.\.\/(?!\.\.\/).*?(?=\/)/g;
26880
+
26881
+ var URLToolkit = { // jshint ignore:line
26882
+ // If opts.alwaysNormalize is true then the path will always be normalized even when it starts with / or //
26883
+ // E.g
26884
+ // With opts.alwaysNormalize = false (default, spec compliant)
26885
+ // http://a.com/b/cd + /e/f/../g => http://a.com/e/f/../g
26886
+ // With opts.alwaysNormalize = true (not spec compliant)
26887
+ // http://a.com/b/cd + /e/f/../g => http://a.com/e/g
26888
+ buildAbsoluteURL: function buildAbsoluteURL(baseURL, relativeURL, opts) {
26889
+ opts = opts || {};
26890
+ // remove any remaining space and CRLF
26891
+ baseURL = baseURL.trim();
26892
+ relativeURL = relativeURL.trim();
26893
+ if (!relativeURL) {
26894
+ // 2a) If the embedded URL is entirely empty, it inherits the
26895
+ // entire base URL (i.e., is set equal to the base URL)
26896
+ // and we are done.
26897
+ if (!opts.alwaysNormalize) {
26898
+ return baseURL;
26899
+ }
26900
+ var basePartsForNormalise = URLToolkit.parseURL(baseURL);
26901
+ if (!basePartsForNormalise) {
26902
+ throw new Error('Error trying to parse base URL.');
26903
+ }
26904
+ basePartsForNormalise.path = URLToolkit.normalizePath(basePartsForNormalise.path);
26905
+ return URLToolkit.buildURLFromParts(basePartsForNormalise);
26906
+ }
26907
+ var relativeParts = URLToolkit.parseURL(relativeURL);
26908
+ if (!relativeParts) {
26909
+ throw new Error('Error trying to parse relative URL.');
26910
+ }
26911
+ if (relativeParts.scheme) {
26912
+ // 2b) If the embedded URL starts with a scheme name, it is
26913
+ // interpreted as an absolute URL and we are done.
26914
+ if (!opts.alwaysNormalize) {
26915
+ return relativeURL;
26916
+ }
26917
+ relativeParts.path = URLToolkit.normalizePath(relativeParts.path);
26918
+ return URLToolkit.buildURLFromParts(relativeParts);
26919
+ }
26920
+ var baseParts = URLToolkit.parseURL(baseURL);
26921
+ if (!baseParts) {
26922
+ throw new Error('Error trying to parse base URL.');
26923
+ }
26924
+ if (!baseParts.netLoc && baseParts.path && baseParts.path[0] !== '/') {
26925
+ // If netLoc missing and path doesn't start with '/', assume everthing before the first '/' is the netLoc
26926
+ // This causes 'example.com/a' to be handled as '//example.com/a' instead of '/example.com/a'
26927
+ var pathParts = FIRST_SEGMENT_REGEX.exec(baseParts.path);
26928
+ baseParts.netLoc = pathParts[1];
26929
+ baseParts.path = pathParts[2];
26930
+ }
26931
+ if (baseParts.netLoc && !baseParts.path) {
26932
+ baseParts.path = '/';
26933
+ }
26934
+ var builtParts = {
26935
+ // 2c) Otherwise, the embedded URL inherits the scheme of
26936
+ // the base URL.
26937
+ scheme: baseParts.scheme,
26938
+ netLoc: relativeParts.netLoc,
26939
+ path: null,
26940
+ params: relativeParts.params,
26941
+ query: relativeParts.query,
26942
+ fragment: relativeParts.fragment
26943
+ };
26944
+ if (!relativeParts.netLoc) {
26945
+ // 3) If the embedded URL's <net_loc> is non-empty, we skip to
26946
+ // Step 7. Otherwise, the embedded URL inherits the <net_loc>
26947
+ // (if any) of the base URL.
26948
+ builtParts.netLoc = baseParts.netLoc;
26949
+ // 4) If the embedded URL path is preceded by a slash "/", the
26950
+ // path is not relative and we skip to Step 7.
26951
+ if (relativeParts.path[0] !== '/') {
26952
+ if (!relativeParts.path) {
26953
+ // 5) If the embedded URL path is empty (and not preceded by a
26954
+ // slash), then the embedded URL inherits the base URL path
26955
+ builtParts.path = baseParts.path;
26956
+ // 5a) if the embedded URL's <params> is non-empty, we skip to
26957
+ // step 7; otherwise, it inherits the <params> of the base
26958
+ // URL (if any) and
26959
+ if (!relativeParts.params) {
26960
+ builtParts.params = baseParts.params;
26961
+ // 5b) if the embedded URL's <query> is non-empty, we skip to
26962
+ // step 7; otherwise, it inherits the <query> of the base
26963
+ // URL (if any) and we skip to step 7.
26964
+ if (!relativeParts.query) {
26965
+ builtParts.query = baseParts.query;
26966
+ }
26967
+ }
26968
+ } else {
26969
+ // 6) The last segment of the base URL's path (anything
26970
+ // following the rightmost slash "/", or the entire path if no
26971
+ // slash is present) is removed and the embedded URL's path is
26972
+ // appended in its place.
26973
+ var baseURLPath = baseParts.path;
26974
+ var newPath = baseURLPath.substring(0, baseURLPath.lastIndexOf('/') + 1) + relativeParts.path;
26975
+ builtParts.path = URLToolkit.normalizePath(newPath);
26976
+ }
26977
+ }
26978
+ }
26979
+ if (builtParts.path === null) {
26980
+ builtParts.path = opts.alwaysNormalize ? URLToolkit.normalizePath(relativeParts.path) : relativeParts.path;
26981
+ }
26982
+ return URLToolkit.buildURLFromParts(builtParts);
26983
+ },
26984
+ parseURL: function parseURL(url) {
26985
+ var parts = URL_REGEX.exec(url);
26986
+ if (!parts) {
26987
+ return null;
26988
+ }
26989
+ return {
26990
+ scheme: parts[1] || '',
26991
+ netLoc: parts[2] || '',
26992
+ path: parts[3] || '',
26993
+ params: parts[4] || '',
26994
+ query: parts[5] || '',
26995
+ fragment: parts[6] || ''
26996
+ };
26997
+ },
26998
+ normalizePath: function normalizePath(path) {
26999
+ // The following operations are
27000
+ // then applied, in order, to the new path:
27001
+ // 6a) All occurrences of "./", where "." is a complete path
27002
+ // segment, are removed.
27003
+ // 6b) If the path ends with "." as a complete path segment,
27004
+ // that "." is removed.
27005
+ path = path.split('').reverse().join('').replace(SLASH_DOT_REGEX, '');
27006
+ // 6c) All occurrences of "<segment>/../", where <segment> is a
27007
+ // complete path segment not equal to "..", are removed.
27008
+ // Removal of these path segments is performed iteratively,
27009
+ // removing the leftmost matching pattern on each iteration,
27010
+ // until no matching pattern remains.
27011
+ // 6d) If the path ends with "<segment>/..", where <segment> is a
27012
+ // complete path segment not equal to "..", that
27013
+ // "<segment>/.." is removed.
27014
+ while (path.length !== (path = path.replace(SLASH_DOT_DOT_REGEX, '')).length) {} // jshint ignore:line
27015
+ return path.split('').reverse().join('');
27016
+ },
27017
+ buildURLFromParts: function buildURLFromParts(parts) {
27018
+ return parts.scheme + parts.netLoc + parts.path + parts.params + parts.query + parts.fragment;
27019
+ }
27020
+ };
27021
+
27022
+ /* jshint ignore:start */
27023
+ module.exports = URLToolkit;
27024
+ })(commonjsGlobal);
27025
+ /* jshint ignore:end */
27026
+ });
27027
+
27028
+ var classCallCheck$1 = function classCallCheck$$1(instance, Constructor) {
27029
+ if (!(instance instanceof Constructor)) {
27030
+ throw new TypeError("Cannot call a class as a function");
27031
+ }
27032
+ };
27033
+
27034
+ var _extends$1 = Object.assign || function (target) {
27035
+ for (var i = 1; i < arguments.length; i++) {
27036
+ var source = arguments[i];
27037
+
27038
+ for (var key in source) {
27039
+ if (Object.prototype.hasOwnProperty.call(source, key)) {
27040
+ target[key] = source[key];
27041
+ }
27042
+ }
27043
+ }
27044
+
27045
+ return target;
27046
+ };
27047
+
27048
+ var inherits$1 = function inherits$$1(subClass, superClass) {
27049
+ if (typeof superClass !== "function" && superClass !== null) {
27050
+ throw new TypeError("Super expression must either be null or a function, not " + (typeof superClass === "undefined" ? "undefined" : _typeof(superClass)));
27051
+ }
27052
+
27053
+ subClass.prototype = Object.create(superClass && superClass.prototype, {
27054
+ constructor: {
27055
+ value: subClass,
27056
+ enumerable: false,
27057
+ writable: true,
27058
+ configurable: true
27059
+ }
27060
+ });
27061
+ if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass;
27062
+ };
27063
+
27064
+ var possibleConstructorReturn$1 = function possibleConstructorReturn$$1(self, call) {
27065
+ if (!self) {
27066
+ throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
27067
+ }
27068
+
27069
+ return call && ((typeof call === "undefined" ? "undefined" : _typeof(call)) === "object" || typeof call === "function") ? call : self;
27070
+ };
27071
+
27072
+ /**
27073
+ * @file stream.js
27074
+ */
27075
+ /**
27076
+ * A lightweight readable stream implemention that handles event dispatching.
27077
+ *
27078
+ * @class Stream
27079
+ */
27080
+ var Stream = function () {
27081
+ function Stream() {
27082
+ classCallCheck$1(this, Stream);
27083
+
27084
+ this.listeners = {};
27085
+ }
27086
+
27087
+ /**
27088
+ * Add a listener for a specified event type.
27089
+ *
27090
+ * @param {String} type the event name
27091
+ * @param {Function} listener the callback to be invoked when an event of
27092
+ * the specified type occurs
27093
+ */
27094
+
27095
+ Stream.prototype.on = function on(type, listener) {
27096
+ if (!this.listeners[type]) {
27097
+ this.listeners[type] = [];
27098
+ }
27099
+ this.listeners[type].push(listener);
27100
+ };
27101
+
27102
+ /**
27103
+ * Remove a listener for a specified event type.
27104
+ *
27105
+ * @param {String} type the event name
27106
+ * @param {Function} listener a function previously registered for this
27107
+ * type of event through `on`
27108
+ * @return {Boolean} if we could turn it off or not
27109
+ */
27110
+
27111
+ Stream.prototype.off = function off(type, listener) {
27112
+ if (!this.listeners[type]) {
27113
+ return false;
27114
+ }
27115
+
27116
+ var index = this.listeners[type].indexOf(listener);
27117
+
27118
+ this.listeners[type].splice(index, 1);
27119
+ return index > -1;
27120
+ };
27121
+
27122
+ /**
27123
+ * Trigger an event of the specified type on this stream. Any additional
27124
+ * arguments to this function are passed as parameters to event listeners.
27125
+ *
27126
+ * @param {String} type the event name
27127
+ */
27128
+
27129
+ Stream.prototype.trigger = function trigger(type) {
27130
+ var callbacks = this.listeners[type];
27131
+ var i = void 0;
27132
+ var length = void 0;
27133
+ var args = void 0;
27134
+
27135
+ if (!callbacks) {
27136
+ return;
27137
+ }
27138
+ // Slicing the arguments on every invocation of this method
27139
+ // can add a significant amount of overhead. Avoid the
27140
+ // intermediate object creation for the common case of a
27141
+ // single callback argument
27142
+ if (arguments.length === 2) {
27143
+ length = callbacks.length;
27144
+ for (i = 0; i < length; ++i) {
27145
+ callbacks[i].call(this, arguments[1]);
27146
+ }
27147
+ } else {
27148
+ args = Array.prototype.slice.call(arguments, 1);
27149
+ length = callbacks.length;
27150
+ for (i = 0; i < length; ++i) {
27151
+ callbacks[i].apply(this, args);
27152
+ }
27153
+ }
27154
+ };
27155
+
27156
+ /**
27157
+ * Destroys the stream and cleans up.
27158
+ */
27159
+
27160
+ Stream.prototype.dispose = function dispose() {
27161
+ this.listeners = {};
27162
+ };
27163
+ /**
27164
+ * Forwards all `data` events on this stream to the destination stream. The
27165
+ * destination stream should provide a method `push` to receive the data
27166
+ * events as they arrive.
27167
+ *
27168
+ * @param {Stream} destination the stream that will receive all `data` events
27169
+ * @see http://nodejs.org/api/stream.html#stream_readable_pipe_destination_options
27170
+ */
27171
+
27172
+ Stream.prototype.pipe = function pipe(destination) {
27173
+ this.on('data', function (data) {
27174
+ destination.push(data);
27175
+ });
27176
+ };
27177
+
27178
+ return Stream;
27179
+ }();
27180
+
27181
+ /**
27182
+ * @file m3u8/line-stream.js
27183
+ */
27184
+ /**
27185
+ * A stream that buffers string input and generates a `data` event for each
27186
+ * line.
27187
+ *
27188
+ * @class LineStream
27189
+ * @extends Stream
27190
+ */
27191
+
27192
+ var LineStream = function (_Stream) {
27193
+ inherits$1(LineStream, _Stream);
27194
+
27195
+ function LineStream() {
27196
+ classCallCheck$1(this, LineStream);
27197
+
27198
+ var _this = possibleConstructorReturn$1(this, _Stream.call(this));
27199
+
27200
+ _this.buffer = '';
27201
+ return _this;
27202
+ }
27203
+
27204
+ /**
27205
+ * Add new data to be parsed.
27206
+ *
27207
+ * @param {String} data the text to process
27208
+ */
27209
+
27210
+ LineStream.prototype.push = function push(data) {
27211
+ var nextNewline = void 0;
27212
+
27213
+ this.buffer += data;
27214
+ nextNewline = this.buffer.indexOf('\n');
27215
+
27216
+ for (; nextNewline > -1; nextNewline = this.buffer.indexOf('\n')) {
27217
+ this.trigger('data', this.buffer.substring(0, nextNewline));
27218
+ this.buffer = this.buffer.substring(nextNewline + 1);
27219
+ }
27220
+ };
27221
+
27222
+ return LineStream;
27223
+ }(Stream);
27224
+
27225
+ /**
27226
+ * @file m3u8/parse-stream.js
27227
+ */
27228
+ /**
27229
+ * "forgiving" attribute list psuedo-grammar:
27230
+ * attributes -> keyvalue (',' keyvalue)*
27231
+ * keyvalue -> key '=' value
27232
+ * key -> [^=]*
27233
+ * value -> '"' [^"]* '"' | [^,]*
27234
+ */
27235
+ var attributeSeparator = function attributeSeparator() {
27236
+ var key = '[^=]*';
27237
+ var value = '"[^"]*"|[^,]*';
27238
+ var keyvalue = '(?:' + key + ')=(?:' + value + ')';
27239
+
27240
+ return new RegExp('(?:^|,)(' + keyvalue + ')');
27241
+ };
27242
+
27243
+ /**
27244
+ * Parse attributes from a line given the seperator
27245
+ *
27246
+ * @param {String} attributes the attibute line to parse
27247
+ */
27248
+ var parseAttributes = function parseAttributes(attributes) {
27249
+ // split the string using attributes as the separator
27250
+ var attrs = attributes.split(attributeSeparator());
27251
+ var result = {};
27252
+ var i = attrs.length;
27253
+ var attr = void 0;
27254
+
27255
+ while (i--) {
27256
+ // filter out unmatched portions of the string
27257
+ if (attrs[i] === '') {
27258
+ continue;
27259
+ }
27260
+
27261
+ // split the key and value
27262
+ attr = /([^=]*)=(.*)/.exec(attrs[i]).slice(1);
27263
+ // trim whitespace and remove optional quotes around the value
27264
+ attr[0] = attr[0].replace(/^\s+|\s+$/g, '');
27265
+ attr[1] = attr[1].replace(/^\s+|\s+$/g, '');
27266
+ attr[1] = attr[1].replace(/^['"](.*)['"]$/g, '$1');
27267
+ result[attr[0]] = attr[1];
27268
+ }
27269
+ return result;
27270
+ };
27271
+
27272
+ /**
27273
+ * A line-level M3U8 parser event stream. It expects to receive input one
27274
+ * line at a time and performs a context-free parse of its contents. A stream
27275
+ * interpretation of a manifest can be useful if the manifest is expected to
27276
+ * be too large to fit comfortably into memory or the entirety of the input
27277
+ * is not immediately available. Otherwise, it's probably much easier to work
27278
+ * with a regular `Parser` object.
27279
+ *
27280
+ * Produces `data` events with an object that captures the parser's
27281
+ * interpretation of the input. That object has a property `tag` that is one
27282
+ * of `uri`, `comment`, or `tag`. URIs only have a single additional
27283
+ * property, `line`, which captures the entirety of the input without
27284
+ * interpretation. Comments similarly have a single additional property
27285
+ * `text` which is the input without the leading `#`.
27286
+ *
27287
+ * Tags always have a property `tagType` which is the lower-cased version of
27288
+ * the M3U8 directive without the `#EXT` or `#EXT-X-` prefix. For instance,
27289
+ * `#EXT-X-MEDIA-SEQUENCE` becomes `media-sequence` when parsed. Unrecognized
27290
+ * tags are given the tag type `unknown` and a single additional property
27291
+ * `data` with the remainder of the input.
27292
+ *
27293
+ * @class ParseStream
27294
+ * @extends Stream
27295
+ */
27296
+
27297
+ var ParseStream = function (_Stream) {
27298
+ inherits$1(ParseStream, _Stream);
27299
+
27300
+ function ParseStream() {
27301
+ classCallCheck$1(this, ParseStream);
27302
+
27303
+ var _this = possibleConstructorReturn$1(this, _Stream.call(this));
27304
+
27305
+ _this.customParsers = [];
27306
+ return _this;
27307
+ }
27308
+
27309
+ /**
27310
+ * Parses an additional line of input.
27311
+ *
27312
+ * @param {String} line a single line of an M3U8 file to parse
27313
+ */
27314
+
27315
+ ParseStream.prototype.push = function push(line) {
27316
+ var match = void 0;
27317
+ var event = void 0;
27318
+
27319
+ // strip whitespace
27320
+ line = line.replace(/^[\u0000\s]+|[\u0000\s]+$/g, '');
27321
+ if (line.length === 0) {
27322
+ // ignore empty lines
27323
+ return;
27324
+ }
27325
+
27326
+ // URIs
27327
+ if (line[0] !== '#') {
27328
+ this.trigger('data', {
27329
+ type: 'uri',
27330
+ uri: line
27331
+ });
27332
+ return;
27333
+ }
27334
+
27335
+ for (var i = 0; i < this.customParsers.length; i++) {
27336
+ if (this.customParsers[i].call(this, line)) {
27337
+ return;
27338
+ }
27339
+ }
27340
+
27341
+ // Comments
27342
+ if (line.indexOf('#EXT') !== 0) {
27343
+ this.trigger('data', {
27344
+ type: 'comment',
27345
+ text: line.slice(1)
27346
+ });
27347
+ return;
27348
+ }
27349
+
27350
+ // strip off any carriage returns here so the regex matching
27351
+ // doesn't have to account for them.
27352
+ line = line.replace('\r', '');
27353
+
27354
+ // Tags
27355
+ match = /^#EXTM3U/.exec(line);
27356
+ if (match) {
27357
+ this.trigger('data', {
27358
+ type: 'tag',
27359
+ tagType: 'm3u'
27360
+ });
27361
+ return;
27362
+ }
27363
+ match = /^#EXTINF:?([0-9\.]*)?,?(.*)?$/.exec(line);
27364
+ if (match) {
27365
+ event = {
27366
+ type: 'tag',
27367
+ tagType: 'inf'
27368
+ };
27369
+ if (match[1]) {
27370
+ event.duration = parseFloat(match[1]);
27371
+ }
27372
+ if (match[2]) {
27373
+ event.title = match[2];
27374
+ }
27375
+ this.trigger('data', event);
27376
+ return;
27377
+ }
27378
+ match = /^#EXT-X-TARGETDURATION:?([0-9.]*)?/.exec(line);
27379
+ if (match) {
27380
+ event = {
27381
+ type: 'tag',
27382
+ tagType: 'targetduration'
27383
+ };
27384
+ if (match[1]) {
27385
+ event.duration = parseInt(match[1], 10);
27386
+ }
27387
+ this.trigger('data', event);
27388
+ return;
27389
+ }
27390
+ match = /^#ZEN-TOTAL-DURATION:?([0-9.]*)?/.exec(line);
27391
+ if (match) {
27392
+ event = {
27393
+ type: 'tag',
27394
+ tagType: 'totalduration'
27395
+ };
27396
+ if (match[1]) {
27397
+ event.duration = parseInt(match[1], 10);
27398
+ }
27399
+ this.trigger('data', event);
27400
+ return;
27401
+ }
27402
+ match = /^#EXT-X-VERSION:?([0-9.]*)?/.exec(line);
27403
+ if (match) {
27404
+ event = {
27405
+ type: 'tag',
27406
+ tagType: 'version'
27407
+ };
27408
+ if (match[1]) {
27409
+ event.version = parseInt(match[1], 10);
27410
+ }
27411
+ this.trigger('data', event);
27412
+ return;
27413
+ }
27414
+ match = /^#EXT-X-MEDIA-SEQUENCE:?(\-?[0-9.]*)?/.exec(line);
27415
+ if (match) {
27416
+ event = {
27417
+ type: 'tag',
27418
+ tagType: 'media-sequence'
27419
+ };
27420
+ if (match[1]) {
27421
+ event.number = parseInt(match[1], 10);
27422
+ }
27423
+ this.trigger('data', event);
27424
+ return;
27425
+ }
27426
+ match = /^#EXT-X-DISCONTINUITY-SEQUENCE:?(\-?[0-9.]*)?/.exec(line);
27427
+ if (match) {
27428
+ event = {
27429
+ type: 'tag',
27430
+ tagType: 'discontinuity-sequence'
27431
+ };
27432
+ if (match[1]) {
27433
+ event.number = parseInt(match[1], 10);
27434
+ }
27435
+ this.trigger('data', event);
27436
+ return;
27437
+ }
27438
+ match = /^#EXT-X-PLAYLIST-TYPE:?(.*)?$/.exec(line);
27439
+ if (match) {
27440
+ event = {
27441
+ type: 'tag',
27442
+ tagType: 'playlist-type'
27443
+ };
27444
+ if (match[1]) {
27445
+ event.playlistType = match[1];
27446
+ }
27447
+ this.trigger('data', event);
27448
+ return;
27449
+ }
27450
+ match = /^#EXT-X-BYTERANGE:?([0-9.]*)?@?([0-9.]*)?/.exec(line);
27451
+ if (match) {
27452
+ event = {
27453
+ type: 'tag',
27454
+ tagType: 'byterange'
27455
+ };
27456
+ if (match[1]) {
27457
+ event.length = parseInt(match[1], 10);
27458
+ }
27459
+ if (match[2]) {
27460
+ event.offset = parseInt(match[2], 10);
27461
+ }
27462
+ this.trigger('data', event);
27463
+ return;
27464
+ }
27465
+ match = /^#EXT-X-ALLOW-CACHE:?(YES|NO)?/.exec(line);
27466
+ if (match) {
27467
+ event = {
27468
+ type: 'tag',
27469
+ tagType: 'allow-cache'
27470
+ };
27471
+ if (match[1]) {
27472
+ event.allowed = !/NO/.test(match[1]);
27473
+ }
27474
+ this.trigger('data', event);
27475
+ return;
27476
+ }
27477
+ match = /^#EXT-X-MAP:?(.*)$/.exec(line);
27478
+ if (match) {
27479
+ event = {
27480
+ type: 'tag',
27481
+ tagType: 'map'
27482
+ };
27483
+
27484
+ if (match[1]) {
27485
+ var attributes = parseAttributes(match[1]);
27486
+
27487
+ if (attributes.URI) {
27488
+ event.uri = attributes.URI;
27489
+ }
27490
+ if (attributes.BYTERANGE) {
27491
+ var _attributes$BYTERANGE = attributes.BYTERANGE.split('@'),
27492
+ length = _attributes$BYTERANGE[0],
27493
+ offset = _attributes$BYTERANGE[1];
27494
+
27495
+ event.byterange = {};
27496
+ if (length) {
27497
+ event.byterange.length = parseInt(length, 10);
27498
+ }
27499
+ if (offset) {
27500
+ event.byterange.offset = parseInt(offset, 10);
27501
+ }
27502
+ }
27503
+ }
27504
+
27505
+ this.trigger('data', event);
27506
+ return;
27507
+ }
27508
+ match = /^#EXT-X-STREAM-INF:?(.*)$/.exec(line);
27509
+ if (match) {
27510
+ event = {
27511
+ type: 'tag',
27512
+ tagType: 'stream-inf'
27513
+ };
27514
+ if (match[1]) {
27515
+ event.attributes = parseAttributes(match[1]);
27516
+
27517
+ if (event.attributes.RESOLUTION) {
27518
+ var split = event.attributes.RESOLUTION.split('x');
27519
+ var resolution = {};
27520
+
27521
+ if (split[0]) {
27522
+ resolution.width = parseInt(split[0], 10);
27523
+ }
27524
+ if (split[1]) {
27525
+ resolution.height = parseInt(split[1], 10);
27526
+ }
27527
+ event.attributes.RESOLUTION = resolution;
27528
+ }
27529
+ if (event.attributes.BANDWIDTH) {
27530
+ event.attributes.BANDWIDTH = parseInt(event.attributes.BANDWIDTH, 10);
27531
+ }
27532
+ if (event.attributes['PROGRAM-ID']) {
27533
+ event.attributes['PROGRAM-ID'] = parseInt(event.attributes['PROGRAM-ID'], 10);
27534
+ }
27535
+ }
27536
+ this.trigger('data', event);
27537
+ return;
27538
+ }
27539
+ match = /^#EXT-X-MEDIA:?(.*)$/.exec(line);
27540
+ if (match) {
27541
+ event = {
27542
+ type: 'tag',
27543
+ tagType: 'media'
27544
+ };
27545
+ if (match[1]) {
27546
+ event.attributes = parseAttributes(match[1]);
27547
+ }
27548
+ this.trigger('data', event);
27549
+ return;
27550
+ }
27551
+ match = /^#EXT-X-ENDLIST/.exec(line);
27552
+ if (match) {
27553
+ this.trigger('data', {
27554
+ type: 'tag',
27555
+ tagType: 'endlist'
27556
+ });
27557
+ return;
27558
+ }
27559
+ match = /^#EXT-X-DISCONTINUITY/.exec(line);
27560
+ if (match) {
27561
+ this.trigger('data', {
27562
+ type: 'tag',
27563
+ tagType: 'discontinuity'
27564
+ });
27565
+ return;
27566
+ }
27567
+ match = /^#EXT-X-PROGRAM-DATE-TIME:?(.*)$/.exec(line);
27568
+ if (match) {
27569
+ event = {
27570
+ type: 'tag',
27571
+ tagType: 'program-date-time'
27572
+ };
27573
+ if (match[1]) {
27574
+ event.dateTimeString = match[1];
27575
+ event.dateTimeObject = new Date(match[1]);
27576
+ }
27577
+ this.trigger('data', event);
27578
+ return;
27579
+ }
27580
+ match = /^#EXT-X-KEY:?(.*)$/.exec(line);
27581
+ if (match) {
27582
+ event = {
27583
+ type: 'tag',
27584
+ tagType: 'key'
27585
+ };
27586
+ if (match[1]) {
27587
+ event.attributes = parseAttributes(match[1]);
27588
+ // parse the IV string into a Uint32Array
27589
+ if (event.attributes.IV) {
27590
+ if (event.attributes.IV.substring(0, 2).toLowerCase() === '0x') {
27591
+ event.attributes.IV = event.attributes.IV.substring(2);
27592
+ }
27593
+
27594
+ event.attributes.IV = event.attributes.IV.match(/.{8}/g);
27595
+ event.attributes.IV[0] = parseInt(event.attributes.IV[0], 16);
27596
+ event.attributes.IV[1] = parseInt(event.attributes.IV[1], 16);
27597
+ event.attributes.IV[2] = parseInt(event.attributes.IV[2], 16);
27598
+ event.attributes.IV[3] = parseInt(event.attributes.IV[3], 16);
27599
+ event.attributes.IV = new Uint32Array(event.attributes.IV);
27600
+ }
27601
+ }
27602
+ this.trigger('data', event);
27603
+ return;
27604
+ }
27605
+ match = /^#EXT-X-START:?(.*)$/.exec(line);
27606
+ if (match) {
27607
+ event = {
27608
+ type: 'tag',
27609
+ tagType: 'start'
27610
+ };
27611
+ if (match[1]) {
27612
+ event.attributes = parseAttributes(match[1]);
27613
+
27614
+ event.attributes['TIME-OFFSET'] = parseFloat(event.attributes['TIME-OFFSET']);
27615
+ event.attributes.PRECISE = /YES/.test(event.attributes.PRECISE);
27616
+ }
27617
+ this.trigger('data', event);
27618
+ return;
27619
+ }
27620
+ match = /^#EXT-X-CUE-OUT-CONT:?(.*)?$/.exec(line);
27621
+ if (match) {
27622
+ event = {
27623
+ type: 'tag',
27624
+ tagType: 'cue-out-cont'
27625
+ };
27626
+ if (match[1]) {
27627
+ event.data = match[1];
27628
+ } else {
27629
+ event.data = '';
27630
+ }
27631
+ this.trigger('data', event);
27632
+ return;
27633
+ }
27634
+ match = /^#EXT-X-CUE-OUT:?(.*)?$/.exec(line);
27635
+ if (match) {
27636
+ event = {
27637
+ type: 'tag',
27638
+ tagType: 'cue-out'
27639
+ };
27640
+ if (match[1]) {
27641
+ event.data = match[1];
27642
+ } else {
27643
+ event.data = '';
27644
+ }
27645
+ this.trigger('data', event);
27646
+ return;
27647
+ }
27648
+ match = /^#EXT-X-CUE-IN:?(.*)?$/.exec(line);
27649
+ if (match) {
27650
+ event = {
27651
+ type: 'tag',
27652
+ tagType: 'cue-in'
27653
+ };
27654
+ if (match[1]) {
27655
+ event.data = match[1];
27656
+ } else {
27657
+ event.data = '';
27658
+ }
27659
+ this.trigger('data', event);
27660
+ return;
27661
+ }
27662
+
27663
+ // unknown tag type
27664
+ this.trigger('data', {
27665
+ type: 'tag',
27666
+ data: line.slice(4)
27667
+ });
27668
+ };
27669
+
27670
+ /**
27671
+ * Add a parser for custom headers
27672
+ *
27673
+ * @param {Object} options a map of options for the added parser
27674
+ * @param {RegExp} options.expression a regular expression to match the custom header
27675
+ * @param {string} options.customType the custom type to register to the output
27676
+ * @param {Function} [options.dataParser] function to parse the line into an object
27677
+ * @param {boolean} [options.segment] should tag data be attached to the segment object
27678
+ */
27679
+
27680
+ ParseStream.prototype.addParser = function addParser(_ref) {
27681
+ var _this2 = this;
27682
+
27683
+ var expression = _ref.expression,
27684
+ customType = _ref.customType,
27685
+ dataParser = _ref.dataParser,
27686
+ segment = _ref.segment;
27687
+
27688
+ if (typeof dataParser !== 'function') {
27689
+ dataParser = function dataParser(line) {
27690
+ return line;
27691
+ };
27692
+ }
27693
+ this.customParsers.push(function (line) {
27694
+ var match = expression.exec(line);
27695
+
27696
+ if (match) {
27697
+ _this2.trigger('data', {
27698
+ type: 'custom',
27699
+ data: dataParser(line),
27700
+ customType: customType,
27701
+ segment: segment
27702
+ });
27703
+ return true;
27704
+ }
27705
+ });
27706
+ };
27707
+
27708
+ return ParseStream;
27709
+ }(Stream);
27710
+
27711
+ /**
27712
+ * @file m3u8/parser.js
27713
+ */
27714
+ /**
27715
+ * A parser for M3U8 files. The current interpretation of the input is
27716
+ * exposed as a property `manifest` on parser objects. It's just two lines to
27717
+ * create and parse a manifest once you have the contents available as a string:
27718
+ *
27719
+ * ```js
27720
+ * var parser = new m3u8.Parser();
27721
+ * parser.push(xhr.responseText);
27722
+ * ```
27723
+ *
27724
+ * New input can later be applied to update the manifest object by calling
27725
+ * `push` again.
27726
+ *
27727
+ * The parser attempts to create a usable manifest object even if the
27728
+ * underlying input is somewhat nonsensical. It emits `info` and `warning`
27729
+ * events during the parse if it encounters input that seems invalid or
27730
+ * requires some property of the manifest object to be defaulted.
27731
+ *
27732
+ * @class Parser
27733
+ * @extends Stream
27734
+ */
27735
+
27736
+ var Parser = function (_Stream) {
27737
+ inherits$1(Parser, _Stream);
27738
+
27739
+ function Parser() {
27740
+ classCallCheck$1(this, Parser);
27741
+
27742
+ var _this = possibleConstructorReturn$1(this, _Stream.call(this));
27743
+
27744
+ _this.lineStream = new LineStream();
27745
+ _this.parseStream = new ParseStream();
27746
+ _this.lineStream.pipe(_this.parseStream);
27747
+
27748
+ /* eslint-disable consistent-this */
27749
+ var self = _this;
27750
+ /* eslint-enable consistent-this */
27751
+ var uris = [];
27752
+ var currentUri = {};
27753
+ // if specified, the active EXT-X-MAP definition
27754
+ var currentMap = void 0;
27755
+ // if specified, the active decryption key
27756
+ var _key = void 0;
27757
+ var noop = function noop() {};
27758
+ var defaultMediaGroups = {
27759
+ 'AUDIO': {},
27760
+ 'VIDEO': {},
27761
+ 'CLOSED-CAPTIONS': {},
27762
+ 'SUBTITLES': {}
27763
+ };
27764
+ // group segments into numbered timelines delineated by discontinuities
27765
+ var currentTimeline = 0;
27766
+
27767
+ // the manifest is empty until the parse stream begins delivering data
27768
+ _this.manifest = {
27769
+ allowCache: true,
27770
+ discontinuityStarts: [],
27771
+ segments: []
27772
+ };
27773
+
27774
+ // update the manifest with the m3u8 entry from the parse stream
27775
+ _this.parseStream.on('data', function (entry) {
27776
+ var mediaGroup = void 0;
27777
+ var rendition = void 0;
27778
+
27779
+ ({
27780
+ tag: function tag() {
27781
+ // switch based on the tag type
27782
+ (({
27783
+ 'allow-cache': function allowCache() {
27784
+ this.manifest.allowCache = entry.allowed;
27785
+ if (!('allowed' in entry)) {
27786
+ this.trigger('info', {
27787
+ message: 'defaulting allowCache to YES'
27788
+ });
27789
+ this.manifest.allowCache = true;
27790
+ }
27791
+ },
27792
+ byterange: function byterange() {
27793
+ var byterange = {};
27794
+
27795
+ if ('length' in entry) {
27796
+ currentUri.byterange = byterange;
27797
+ byterange.length = entry.length;
27798
+
27799
+ if (!('offset' in entry)) {
27800
+ this.trigger('info', {
27801
+ message: 'defaulting offset to zero'
27802
+ });
27803
+ entry.offset = 0;
27804
+ }
27805
+ }
27806
+ if ('offset' in entry) {
27807
+ currentUri.byterange = byterange;
27808
+ byterange.offset = entry.offset;
27809
+ }
27810
+ },
27811
+ endlist: function endlist() {
27812
+ this.manifest.endList = true;
27813
+ },
27814
+ inf: function inf() {
27815
+ if (!('mediaSequence' in this.manifest)) {
27816
+ this.manifest.mediaSequence = 0;
27817
+ this.trigger('info', {
27818
+ message: 'defaulting media sequence to zero'
27819
+ });
27820
+ }
27821
+ if (!('discontinuitySequence' in this.manifest)) {
27822
+ this.manifest.discontinuitySequence = 0;
27823
+ this.trigger('info', {
27824
+ message: 'defaulting discontinuity sequence to zero'
27825
+ });
27826
+ }
27827
+ if (entry.duration > 0) {
27828
+ currentUri.duration = entry.duration;
27829
+ }
27830
+
27831
+ if (entry.duration === 0) {
27832
+ currentUri.duration = 0.01;
27833
+ this.trigger('info', {
27834
+ message: 'updating zero segment duration to a small value'
27835
+ });
27836
+ }
27837
+
27838
+ this.manifest.segments = uris;
27839
+ },
27840
+ key: function key() {
27841
+ if (!entry.attributes) {
27842
+ this.trigger('warn', {
27843
+ message: 'ignoring key declaration without attribute list'
27844
+ });
27845
+ return;
27846
+ }
27847
+ // clear the active encryption key
27848
+ if (entry.attributes.METHOD === 'NONE') {
27849
+ _key = null;
27850
+ return;
27851
+ }
27852
+ if (!entry.attributes.URI) {
27853
+ this.trigger('warn', {
27854
+ message: 'ignoring key declaration without URI'
27855
+ });
27856
+ return;
27857
+ }
27858
+ if (!entry.attributes.METHOD) {
27859
+ this.trigger('warn', {
27860
+ message: 'defaulting key method to AES-128'
27861
+ });
27862
+ }
27863
+
27864
+ // setup an encryption key for upcoming segments
27865
+ _key = {
27866
+ method: entry.attributes.METHOD || 'AES-128',
27867
+ uri: entry.attributes.URI
27868
+ };
27869
+
27870
+ if (typeof entry.attributes.IV !== 'undefined') {
27871
+ _key.iv = entry.attributes.IV;
27872
+ }
27873
+ },
27874
+ 'media-sequence': function mediaSequence() {
27875
+ if (!isFinite(entry.number)) {
27876
+ this.trigger('warn', {
27877
+ message: 'ignoring invalid media sequence: ' + entry.number
27878
+ });
27879
+ return;
27880
+ }
27881
+ this.manifest.mediaSequence = entry.number;
27882
+ },
27883
+ 'discontinuity-sequence': function discontinuitySequence() {
27884
+ if (!isFinite(entry.number)) {
27885
+ this.trigger('warn', {
27886
+ message: 'ignoring invalid discontinuity sequence: ' + entry.number
27887
+ });
27888
+ return;
27889
+ }
27890
+ this.manifest.discontinuitySequence = entry.number;
27891
+ currentTimeline = entry.number;
27892
+ },
27893
+ 'playlist-type': function playlistType() {
27894
+ if (!/VOD|EVENT/.test(entry.playlistType)) {
27895
+ this.trigger('warn', {
27896
+ message: 'ignoring unknown playlist type: ' + entry.playlist
27897
+ });
27898
+ return;
27899
+ }
27900
+ this.manifest.playlistType = entry.playlistType;
27901
+ },
27902
+ map: function map() {
27903
+ currentMap = {};
27904
+ if (entry.uri) {
27905
+ currentMap.uri = entry.uri;
27906
+ }
27907
+ if (entry.byterange) {
27908
+ currentMap.byterange = entry.byterange;
27909
+ }
27910
+ },
27911
+ 'stream-inf': function streamInf() {
27912
+ this.manifest.playlists = uris;
27913
+ this.manifest.mediaGroups = this.manifest.mediaGroups || defaultMediaGroups;
27914
+
27915
+ if (!entry.attributes) {
27916
+ this.trigger('warn', {
27917
+ message: 'ignoring empty stream-inf attributes'
27918
+ });
27919
+ return;
27920
+ }
27921
+
27922
+ if (!currentUri.attributes) {
27923
+ currentUri.attributes = {};
27924
+ }
27925
+ _extends$1(currentUri.attributes, entry.attributes);
27926
+ },
27927
+ media: function media() {
27928
+ this.manifest.mediaGroups = this.manifest.mediaGroups || defaultMediaGroups;
27929
+
27930
+ if (!(entry.attributes && entry.attributes.TYPE && entry.attributes['GROUP-ID'] && entry.attributes.NAME)) {
27931
+ this.trigger('warn', {
27932
+ message: 'ignoring incomplete or missing media group'
27933
+ });
27934
+ return;
27935
+ }
27936
+
27937
+ // find the media group, creating defaults as necessary
27938
+ var mediaGroupType = this.manifest.mediaGroups[entry.attributes.TYPE];
27939
+
27940
+ mediaGroupType[entry.attributes['GROUP-ID']] = mediaGroupType[entry.attributes['GROUP-ID']] || {};
27941
+ mediaGroup = mediaGroupType[entry.attributes['GROUP-ID']];
27942
+
27943
+ // collect the rendition metadata
27944
+ rendition = {
27945
+ 'default': /yes/i.test(entry.attributes.DEFAULT)
27946
+ };
27947
+ if (rendition['default']) {
27948
+ rendition.autoselect = true;
27949
+ } else {
27950
+ rendition.autoselect = /yes/i.test(entry.attributes.AUTOSELECT);
27951
+ }
27952
+ if (entry.attributes.LANGUAGE) {
27953
+ rendition.language = entry.attributes.LANGUAGE;
27954
+ }
27955
+ if (entry.attributes.URI) {
27956
+ rendition.uri = entry.attributes.URI;
27957
+ }
27958
+ if (entry.attributes['INSTREAM-ID']) {
27959
+ rendition.instreamId = entry.attributes['INSTREAM-ID'];
27960
+ }
27961
+ if (entry.attributes.CHARACTERISTICS) {
27962
+ rendition.characteristics = entry.attributes.CHARACTERISTICS;
27963
+ }
27964
+ if (entry.attributes.FORCED) {
27965
+ rendition.forced = /yes/i.test(entry.attributes.FORCED);
27966
+ }
27967
+
27968
+ // insert the new rendition
27969
+ mediaGroup[entry.attributes.NAME] = rendition;
27970
+ },
27971
+ discontinuity: function discontinuity() {
27972
+ currentTimeline += 1;
27973
+ currentUri.discontinuity = true;
27974
+ this.manifest.discontinuityStarts.push(uris.length);
27975
+ },
27976
+ 'program-date-time': function programDateTime() {
27977
+ if (typeof this.manifest.dateTimeString === 'undefined') {
27978
+ // PROGRAM-DATE-TIME is a media-segment tag, but for backwards
27979
+ // compatibility, we add the first occurence of the PROGRAM-DATE-TIME tag
27980
+ // to the manifest object
27981
+ // TODO: Consider removing this in future major version
27982
+ this.manifest.dateTimeString = entry.dateTimeString;
27983
+ this.manifest.dateTimeObject = entry.dateTimeObject;
27984
+ }
27985
+
27986
+ currentUri.dateTimeString = entry.dateTimeString;
27987
+ currentUri.dateTimeObject = entry.dateTimeObject;
27988
+ },
27989
+ targetduration: function targetduration() {
27990
+ if (!isFinite(entry.duration) || entry.duration < 0) {
27991
+ this.trigger('warn', {
27992
+ message: 'ignoring invalid target duration: ' + entry.duration
27993
+ });
27994
+ return;
27995
+ }
27996
+ this.manifest.targetDuration = entry.duration;
27997
+ },
27998
+ totalduration: function totalduration() {
27999
+ if (!isFinite(entry.duration) || entry.duration < 0) {
28000
+ this.trigger('warn', {
28001
+ message: 'ignoring invalid total duration: ' + entry.duration
28002
+ });
28003
+ return;
28004
+ }
28005
+ this.manifest.totalDuration = entry.duration;
28006
+ },
28007
+ start: function start() {
28008
+ if (!entry.attributes || isNaN(entry.attributes['TIME-OFFSET'])) {
28009
+ this.trigger('warn', {
28010
+ message: 'ignoring start declaration without appropriate attribute list'
28011
+ });
28012
+ return;
28013
+ }
28014
+ this.manifest.start = {
28015
+ timeOffset: entry.attributes['TIME-OFFSET'],
28016
+ precise: entry.attributes.PRECISE
28017
+ };
28018
+ },
28019
+ 'cue-out': function cueOut() {
28020
+ currentUri.cueOut = entry.data;
28021
+ },
28022
+ 'cue-out-cont': function cueOutCont() {
28023
+ currentUri.cueOutCont = entry.data;
28024
+ },
28025
+ 'cue-in': function cueIn() {
28026
+ currentUri.cueIn = entry.data;
28027
+ }
28028
+ })[entry.tagType] || noop).call(self);
28029
+ },
28030
+ uri: function uri() {
28031
+ currentUri.uri = entry.uri;
28032
+ uris.push(currentUri);
28033
+
28034
+ // if no explicit duration was declared, use the target duration
28035
+ if (this.manifest.targetDuration && !('duration' in currentUri)) {
28036
+ this.trigger('warn', {
28037
+ message: 'defaulting segment duration to the target duration'
28038
+ });
28039
+ currentUri.duration = this.manifest.targetDuration;
28040
+ }
28041
+ // annotate with encryption information, if necessary
28042
+ if (_key) {
28043
+ currentUri.key = _key;
28044
+ }
28045
+ currentUri.timeline = currentTimeline;
28046
+ // annotate with initialization segment information, if necessary
28047
+ if (currentMap) {
28048
+ currentUri.map = currentMap;
28049
+ }
28050
+
28051
+ // prepare for the next URI
28052
+ currentUri = {};
28053
+ },
28054
+ comment: function comment() {
28055
+ // comments are not important for playback
28056
+ },
28057
+ custom: function custom() {
28058
+ // if this is segment-level data attach the output to the segment
28059
+ if (entry.segment) {
28060
+ currentUri.custom = currentUri.custom || {};
28061
+ currentUri.custom[entry.customType] = entry.data;
28062
+ // if this is manifest-level data attach to the top level manifest object
28063
+ } else {
28064
+ this.manifest.custom = this.manifest.custom || {};
28065
+ this.manifest.custom[entry.customType] = entry.data;
28066
+ }
28067
+ }
28068
+ })[entry.type].call(self);
28069
+ });
28070
+ return _this;
28071
+ }
28072
+
28073
+ /**
28074
+ * Parse the input string and update the manifest object.
28075
+ *
28076
+ * @param {String} chunk a potentially incomplete portion of the manifest
28077
+ */
28078
+
28079
+ Parser.prototype.push = function push(chunk) {
28080
+ this.lineStream.push(chunk);
28081
+ };
28082
+
28083
+ /**
28084
+ * Flush any remaining input. This can be handy if the last line of an M3U8
28085
+ * manifest did not contain a trailing newline but the file has been
28086
+ * completely received.
28087
+ */
28088
+
28089
+ Parser.prototype.end = function end() {
28090
+ // flush any buffered input
28091
+ this.lineStream.push('\n');
28092
+ };
28093
+ /**
28094
+ * Add an additional parser for non-standard tags
28095
+ *
28096
+ * @param {Object} options a map of options for the added parser
28097
+ * @param {RegExp} options.expression a regular expression to match the custom header
28098
+ * @param {string} options.type the type to register to the output
28099
+ * @param {Function} [options.dataParser] function to parse the line into an object
28100
+ * @param {boolean} [options.segment] should tag data be attached to the segment object
28101
+ */
28102
+
28103
+ Parser.prototype.addParser = function addParser(options) {
28104
+ this.parseStream.addParser(options);
28105
+ };
28106
+
28107
+ return Parser;
28108
+ }(Stream);
28109
+
28110
+ /**
28111
+ * mpd-parser
28112
+ * @version 0.6.1
28113
+ * @copyright 2018 Brightcove, Inc
28114
+ * @license Apache-2.0
28115
+ */
28116
+
28117
+ var formatAudioPlaylist = function formatAudioPlaylist(_ref) {
28118
+ var _attributes;
28119
+
28120
+ var attributes = _ref.attributes,
28121
+ segments = _ref.segments;
28122
+
28123
+ var playlist = {
28124
+ attributes: (_attributes = {
28125
+ NAME: attributes.id,
28126
+ BANDWIDTH: attributes.bandwidth,
28127
+ CODECS: attributes.codecs
28128
+ }, _attributes['PROGRAM-ID'] = 1, _attributes),
28129
+ uri: '',
28130
+ endList: (attributes.type || 'static') === 'static',
28131
+ timeline: attributes.periodIndex,
28132
+ resolvedUri: '',
28133
+ targetDuration: attributes.duration,
28134
+ segments: segments,
28135
+ mediaSequence: segments.length ? segments[0].number : 1
28136
+ };
28137
+
28138
+ if (attributes.contentProtection) {
28139
+ playlist.contentProtection = attributes.contentProtection;
28140
+ }
28141
+
28142
+ return playlist;
28143
+ };
28144
+
28145
+ var formatVttPlaylist = function formatVttPlaylist(_ref2) {
28146
+ var _attributes2;
28147
+
28148
+ var attributes = _ref2.attributes,
28149
+ segments = _ref2.segments;
28150
+
28151
+ if (typeof segments === 'undefined') {
28152
+ // vtt tracks may use single file in BaseURL
28153
+ segments = [{
28154
+ uri: attributes.baseUrl,
28155
+ timeline: attributes.periodIndex,
28156
+ resolvedUri: attributes.baseUrl || '',
28157
+ duration: attributes.sourceDuration,
28158
+ number: 0
28159
+ }];
28160
+ // targetDuration should be the same duration as the only segment
28161
+ attributes.duration = attributes.sourceDuration;
28162
+ }
28163
+ return {
28164
+ attributes: (_attributes2 = {
28165
+ NAME: attributes.id,
28166
+ BANDWIDTH: attributes.bandwidth
28167
+ }, _attributes2['PROGRAM-ID'] = 1, _attributes2),
28168
+ uri: '',
28169
+ endList: (attributes.type || 'static') === 'static',
28170
+ timeline: attributes.periodIndex,
28171
+ resolvedUri: attributes.baseUrl || '',
28172
+ targetDuration: attributes.duration,
28173
+ segments: segments,
28174
+ mediaSequence: segments.length ? segments[0].number : 1
28175
+ };
28176
+ };
28177
+
28178
+ var organizeAudioPlaylists = function organizeAudioPlaylists(playlists) {
28179
+ return playlists.reduce(function (a, playlist) {
28180
+ var role = playlist.attributes.role && playlist.attributes.role.value || 'main';
28181
+ var language = playlist.attributes.lang || '';
28182
+
28183
+ var label = 'main';
28184
+
28185
+ if (language) {
28186
+ label = playlist.attributes.lang + ' (' + role + ')';
28187
+ }
28188
+
28189
+ // skip if we already have the highest quality audio for a language
28190
+ if (a[label] && a[label].playlists[0].attributes.BANDWIDTH > playlist.attributes.bandwidth) {
28191
+ return a;
28192
+ }
28193
+
28194
+ a[label] = {
28195
+ language: language,
28196
+ autoselect: true,
28197
+ 'default': role === 'main',
28198
+ playlists: [formatAudioPlaylist(playlist)],
28199
+ uri: ''
28200
+ };
28201
+
28202
+ return a;
28203
+ }, {});
28204
+ };
28205
+
28206
+ var organizeVttPlaylists = function organizeVttPlaylists(playlists) {
28207
+ return playlists.reduce(function (a, playlist) {
28208
+ var label = playlist.attributes.lang || 'text';
28209
+
28210
+ // skip if we already have subtitles
28211
+ if (a[label]) {
28212
+ return a;
28213
+ }
28214
+
28215
+ a[label] = {
28216
+ language: label,
28217
+ 'default': false,
28218
+ autoselect: false,
28219
+ playlists: [formatVttPlaylist(playlist)],
28220
+ uri: ''
28221
+ };
28222
+
28223
+ return a;
28224
+ }, {});
28225
+ };
28226
+
28227
+ var formatVideoPlaylist = function formatVideoPlaylist(_ref3) {
28228
+ var _attributes3;
28229
+
28230
+ var attributes = _ref3.attributes,
28231
+ segments = _ref3.segments;
28232
+
28233
+ var playlist = {
28234
+ attributes: (_attributes3 = {
28235
+ NAME: attributes.id,
28236
+ AUDIO: 'audio',
28237
+ SUBTITLES: 'subs',
28238
+ RESOLUTION: {
28239
+ width: attributes.width,
28240
+ height: attributes.height
28241
+ },
28242
+ CODECS: attributes.codecs,
28243
+ BANDWIDTH: attributes.bandwidth
28244
+ }, _attributes3['PROGRAM-ID'] = 1, _attributes3),
28245
+ uri: '',
28246
+ endList: (attributes.type || 'static') === 'static',
28247
+ timeline: attributes.periodIndex,
28248
+ resolvedUri: '',
28249
+ targetDuration: attributes.duration,
28250
+ segments: segments,
28251
+ mediaSequence: segments.length ? segments[0].number : 1
28252
+ };
28253
+
28254
+ if (attributes.contentProtection) {
28255
+ playlist.contentProtection = attributes.contentProtection;
28256
+ }
28257
+
28258
+ return playlist;
28259
+ };
28260
+
28261
+ var toM3u8 = function toM3u8(dashPlaylists) {
28262
+ var _mediaGroups;
28263
+
28264
+ if (!dashPlaylists.length) {
28265
+ return {};
28266
+ }
28267
+
28268
+ // grab all master attributes
28269
+ var _dashPlaylists$0$attr = dashPlaylists[0].attributes,
28270
+ duration = _dashPlaylists$0$attr.sourceDuration,
28271
+ _dashPlaylists$0$attr2 = _dashPlaylists$0$attr.minimumUpdatePeriod,
28272
+ minimumUpdatePeriod = _dashPlaylists$0$attr2 === undefined ? 0 : _dashPlaylists$0$attr2;
28273
+
28274
+ var videoOnly = function videoOnly(_ref4) {
28275
+ var attributes = _ref4.attributes;
28276
+ return attributes.mimeType === 'video/mp4' || attributes.contentType === 'video';
28277
+ };
28278
+ var audioOnly = function audioOnly(_ref5) {
28279
+ var attributes = _ref5.attributes;
28280
+ return attributes.mimeType === 'audio/mp4' || attributes.contentType === 'audio';
28281
+ };
28282
+ var vttOnly = function vttOnly(_ref6) {
28283
+ var attributes = _ref6.attributes;
28284
+ return attributes.mimeType === 'text/vtt' || attributes.contentType === 'text';
28285
+ };
28286
+
28287
+ var videoPlaylists = dashPlaylists.filter(videoOnly).map(formatVideoPlaylist);
28288
+ var audioPlaylists = dashPlaylists.filter(audioOnly);
28289
+ var vttPlaylists = dashPlaylists.filter(vttOnly);
28290
+
28291
+ var master = {
28292
+ allowCache: true,
28293
+ discontinuityStarts: [],
28294
+ segments: [],
28295
+ endList: true,
28296
+ mediaGroups: (_mediaGroups = {
28297
+ AUDIO: {},
28298
+ VIDEO: {}
28299
+ }, _mediaGroups['CLOSED-CAPTIONS'] = {}, _mediaGroups.SUBTITLES = {}, _mediaGroups),
28300
+ uri: '',
28301
+ duration: duration,
28302
+ playlists: videoPlaylists,
28303
+ minimumUpdatePeriod: minimumUpdatePeriod * 1000
28304
+ };
28305
+
28306
+ if (audioPlaylists.length) {
28307
+ master.mediaGroups.AUDIO.audio = organizeAudioPlaylists(audioPlaylists);
28308
+ }
28309
+
28310
+ if (vttPlaylists.length) {
28311
+ master.mediaGroups.SUBTITLES.subs = organizeVttPlaylists(vttPlaylists);
28312
+ }
28313
+
28314
+ return master;
28315
+ };
28316
+
28317
+ var _typeof$1 = typeof Symbol === "function" && _typeof(Symbol.iterator) === "symbol" ? function (obj) {
28318
+ return typeof obj === 'undefined' ? 'undefined' : _typeof(obj);
28319
+ } : function (obj) {
28320
+ return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj === 'undefined' ? 'undefined' : _typeof(obj);
28321
+ };
28322
+
28323
+ var isObject$1 = function isObject(obj) {
28324
+ return !!obj && (typeof obj === 'undefined' ? 'undefined' : _typeof$1(obj)) === 'object';
28325
+ };
28326
+
28327
+ var merge = function merge() {
28328
+ for (var _len = arguments.length, objects = Array(_len), _key = 0; _key < _len; _key++) {
28329
+ objects[_key] = arguments[_key];
28330
+ }
28331
+
28332
+ return objects.reduce(function (result, source) {
28333
+
28334
+ Object.keys(source).forEach(function (key) {
28335
+
28336
+ if (Array.isArray(result[key]) && Array.isArray(source[key])) {
28337
+ result[key] = result[key].concat(source[key]);
28338
+ } else if (isObject$1(result[key]) && isObject$1(source[key])) {
28339
+ result[key] = merge(result[key], source[key]);
28340
+ } else {
28341
+ result[key] = source[key];
28342
+ }
28343
+ });
28344
+ return result;
28345
+ }, {});
28346
+ };
28347
+
28348
+ var resolveUrl = function resolveUrl(baseUrl, relativeUrl) {
28349
+ // return early if we don't need to resolve
28350
+ if (/^[a-z]+:/i.test(relativeUrl)) {
28351
+ return relativeUrl;
28352
+ }
28353
+
28354
+ // if the base URL is relative then combine with the current location
28355
+ if (!/\/\//i.test(baseUrl)) {
28356
+ baseUrl = urlToolkit.buildAbsoluteURL(window_1.location.href, baseUrl);
28357
+ }
28358
+
28359
+ return urlToolkit.buildAbsoluteURL(baseUrl, relativeUrl);
28360
+ };
28361
+
28362
+ /**
28363
+ * @typedef {Object} SingleUri
28364
+ * @property {string} uri - relative location of segment
28365
+ * @property {string} resolvedUri - resolved location of segment
28366
+ * @property {Object} byterange - Object containing information on how to make byte range
28367
+ * requests following byte-range-spec per RFC2616.
28368
+ * @property {String} byterange.length - length of range request
28369
+ * @property {String} byterange.offset - byte offset of range request
28370
+ *
28371
+ * @see https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35.1
28372
+ */
28373
+
28374
+ /**
28375
+ * Converts a URLType node (5.3.9.2.3 Table 13) to a segment object
28376
+ * that conforms to how m3u8-parser is structured
28377
+ *
28378
+ * @see https://github.com/videojs/m3u8-parser
28379
+ *
28380
+ * @param {string} baseUrl - baseUrl provided by <BaseUrl> nodes
28381
+ * @param {string} source - source url for segment
28382
+ * @param {string} range - optional range used for range calls, follows
28383
+ * @return {SingleUri} full segment information transformed into a format similar
28384
+ * to m3u8-parser
28385
+ */
28386
+ var urlTypeToSegment = function urlTypeToSegment(_ref) {
28387
+ var _ref$baseUrl = _ref.baseUrl,
28388
+ baseUrl = _ref$baseUrl === undefined ? '' : _ref$baseUrl,
28389
+ _ref$source = _ref.source,
28390
+ source = _ref$source === undefined ? '' : _ref$source,
28391
+ _ref$range = _ref.range,
28392
+ range = _ref$range === undefined ? '' : _ref$range;
28393
+
28394
+ var init = {
28395
+ uri: source,
28396
+ resolvedUri: resolveUrl(baseUrl || '', source)
28397
+ };
28398
+
28399
+ if (range) {
28400
+ var ranges = range.split('-');
28401
+ var startRange = parseInt(ranges[0], 10);
28402
+ var endRange = parseInt(ranges[1], 10);
28403
+
28404
+ init.byterange = {
28405
+ length: endRange - startRange,
28406
+ offset: startRange
28407
+ };
28408
+ }
28409
+
28410
+ return init;
28411
+ };
28412
+
28413
+ /**
28414
+ * Calculates the R (repetition) value for a live stream (for the final segment
28415
+ * in a manifest where the r value is negative 1)
28416
+ *
28417
+ * @param {Object} attributes
28418
+ * Object containing all inherited attributes from parent elements with attribute
28419
+ * names as keys
28420
+ * @param {number} time
28421
+ * current time (typically the total time up until the final segment)
28422
+ * @param {number} duration
28423
+ * duration property for the given <S />
28424
+ *
28425
+ * @return {number}
28426
+ * R value to reach the end of the given period
28427
+ */
28428
+ var getLiveRValue = function getLiveRValue(attributes, time, duration) {
28429
+ var NOW = attributes.NOW,
28430
+ clientOffset = attributes.clientOffset,
28431
+ availabilityStartTime = attributes.availabilityStartTime,
28432
+ _attributes$timescale = attributes.timescale,
28433
+ timescale = _attributes$timescale === undefined ? 1 : _attributes$timescale,
28434
+ _attributes$start = attributes.start,
28435
+ start = _attributes$start === undefined ? 0 : _attributes$start,
28436
+ _attributes$minimumUp = attributes.minimumUpdatePeriod,
28437
+ minimumUpdatePeriod = _attributes$minimumUp === undefined ? 0 : _attributes$minimumUp;
28438
+
28439
+ var now = (NOW + clientOffset) / 1000;
28440
+ var periodStartWC = availabilityStartTime + start;
28441
+ var periodEndWC = now + minimumUpdatePeriod;
28442
+ var periodDuration = periodEndWC - periodStartWC;
28443
+
28444
+ return Math.ceil((periodDuration * timescale - time) / duration);
28445
+ };
28446
+
28447
+ /**
28448
+ * Uses information provided by SegmentTemplate.SegmentTimeline to determine segment
28449
+ * timing and duration
28450
+ *
28451
+ * @param {Object} attributes
28452
+ * Object containing all inherited attributes from parent elements with attribute
28453
+ * names as keys
28454
+ * @param {Object[]} segmentTimeline
28455
+ * List of objects representing the attributes of each S element contained within
28456
+ *
28457
+ * @return {{number: number, duration: number, time: number, timeline: number}[]}
28458
+ * List of Objects with segment timing and duration info
28459
+ */
28460
+ var parseByTimeline = function parseByTimeline(attributes, segmentTimeline) {
28461
+ var _attributes$type = attributes.type,
28462
+ type = _attributes$type === undefined ? 'static' : _attributes$type,
28463
+ _attributes$minimumUp2 = attributes.minimumUpdatePeriod,
28464
+ minimumUpdatePeriod = _attributes$minimumUp2 === undefined ? 0 : _attributes$minimumUp2,
28465
+ _attributes$media = attributes.media,
28466
+ media = _attributes$media === undefined ? '' : _attributes$media,
28467
+ sourceDuration = attributes.sourceDuration,
28468
+ _attributes$timescale2 = attributes.timescale,
28469
+ timescale = _attributes$timescale2 === undefined ? 1 : _attributes$timescale2,
28470
+ _attributes$startNumb = attributes.startNumber,
28471
+ startNumber = _attributes$startNumb === undefined ? 1 : _attributes$startNumb,
28472
+ timeline = attributes.periodIndex;
28473
+
28474
+ var segments = [];
28475
+ var time = -1;
28476
+
28477
+ for (var sIndex = 0; sIndex < segmentTimeline.length; sIndex++) {
28478
+ var S = segmentTimeline[sIndex];
28479
+ var duration = S.d;
28480
+ var repeat = S.r || 0;
28481
+ var segmentTime = S.t || 0;
28482
+
28483
+ if (time < 0) {
28484
+ // first segment
28485
+ time = segmentTime;
28486
+ }
28487
+
28488
+ if (segmentTime && segmentTime > time) {
28489
+ // discontinuity
28490
+
28491
+ // TODO: How to handle this type of discontinuity
28492
+ // timeline++ here would treat it like HLS discontuity and content would
28493
+ // get appended without gap
28494
+ // E.G.
28495
+ // <S t="0" d="1" />
28496
+ // <S d="1" />
28497
+ // <S d="1" />
28498
+ // <S t="5" d="1" />
28499
+ // would have $Time$ values of [0, 1, 2, 5]
28500
+ // should this be appened at time positions [0, 1, 2, 3],(#EXT-X-DISCONTINUITY)
28501
+ // or [0, 1, 2, gap, gap, 5]? (#EXT-X-GAP)
28502
+ // does the value of sourceDuration consider this when calculating arbitrary
28503
+ // negative @r repeat value?
28504
+ // E.G. Same elements as above with this added at the end
28505
+ // <S d="1" r="-1" />
28506
+ // with a sourceDuration of 10
28507
+ // Would the 2 gaps be included in the time duration calculations resulting in
28508
+ // 8 segments with $Time$ values of [0, 1, 2, 5, 6, 7, 8, 9] or 10 segments
28509
+ // with $Time$ values of [0, 1, 2, 5, 6, 7, 8, 9, 10, 11] ?
28510
+
28511
+ time = segmentTime;
28512
+ }
28513
+
28514
+ var count = void 0;
28515
+
28516
+ if (repeat < 0) {
28517
+ var nextS = sIndex + 1;
28518
+
28519
+ if (nextS === segmentTimeline.length) {
28520
+ // last segment
28521
+ if (type === 'dynamic' && minimumUpdatePeriod > 0 && media.indexOf('$Number$') > 0) {
28522
+ count = getLiveRValue(attributes, time, duration);
28523
+ } else {
28524
+ // TODO: This may be incorrect depending on conclusion of TODO above
28525
+ count = (sourceDuration * timescale - time) / duration;
28526
+ }
28527
+ } else {
28528
+ count = (segmentTimeline[nextS].t - time) / duration;
28529
+ }
28530
+ } else {
28531
+ count = repeat + 1;
28532
+ }
28533
+
28534
+ var end = startNumber + segments.length + count;
28535
+ var number = startNumber + segments.length;
28536
+
28537
+ while (number < end) {
28538
+ segments.push({ number: number, duration: duration / timescale, time: time, timeline: timeline });
28539
+ time += duration;
28540
+ number++;
28541
+ }
28542
+ }
28543
+
28544
+ return segments;
28545
+ };
28546
+
28547
+ var range = function range(start, end) {
28548
+ var result = [];
28549
+
28550
+ for (var i = start; i < end; i++) {
28551
+ result.push(i);
28552
+ }
28553
+
28554
+ return result;
28555
+ };
28556
+
28557
+ var flatten = function flatten(lists) {
28558
+ return lists.reduce(function (x, y) {
28559
+ return x.concat(y);
28560
+ }, []);
28561
+ };
28562
+
28563
+ var from = function from(list) {
28564
+ if (!list.length) {
28565
+ return [];
28566
+ }
28567
+
28568
+ var result = [];
28569
+
28570
+ for (var i = 0; i < list.length; i++) {
28571
+ result.push(list[i]);
28572
+ }
28573
+
28574
+ return result;
28575
+ };
28576
+
28577
+ /**
28578
+ * Functions for calculating the range of available segments in static and dynamic
28579
+ * manifests.
28580
+ */
28581
+ var segmentRange = {
28582
+ /**
28583
+ * Returns the entire range of available segments for a static MPD
28584
+ *
28585
+ * @param {Object} attributes
28586
+ * Inheritied MPD attributes
28587
+ * @return {{ start: number, end: number }}
28588
+ * The start and end numbers for available segments
28589
+ */
28590
+ 'static': function _static(attributes) {
28591
+ var duration = attributes.duration,
28592
+ _attributes$timescale = attributes.timescale,
28593
+ timescale = _attributes$timescale === undefined ? 1 : _attributes$timescale,
28594
+ sourceDuration = attributes.sourceDuration;
28595
+
28596
+ return {
28597
+ start: 0,
28598
+ end: Math.ceil(sourceDuration / (duration / timescale))
28599
+ };
28600
+ },
28601
+
28602
+ /**
28603
+ * Returns the current live window range of available segments for a dynamic MPD
28604
+ *
28605
+ * @param {Object} attributes
28606
+ * Inheritied MPD attributes
28607
+ * @return {{ start: number, end: number }}
28608
+ * The start and end numbers for available segments
28609
+ */
28610
+ dynamic: function dynamic(attributes) {
28611
+ var NOW = attributes.NOW,
28612
+ clientOffset = attributes.clientOffset,
28613
+ availabilityStartTime = attributes.availabilityStartTime,
28614
+ _attributes$timescale2 = attributes.timescale,
28615
+ timescale = _attributes$timescale2 === undefined ? 1 : _attributes$timescale2,
28616
+ duration = attributes.duration,
28617
+ _attributes$start = attributes.start,
28618
+ start = _attributes$start === undefined ? 0 : _attributes$start,
28619
+ _attributes$minimumUp = attributes.minimumUpdatePeriod,
28620
+ minimumUpdatePeriod = _attributes$minimumUp === undefined ? 0 : _attributes$minimumUp,
28621
+ _attributes$timeShift = attributes.timeShiftBufferDepth,
28622
+ timeShiftBufferDepth = _attributes$timeShift === undefined ? Infinity : _attributes$timeShift;
28623
+
28624
+ var now = (NOW + clientOffset) / 1000;
28625
+ var periodStartWC = availabilityStartTime + start;
28626
+ var periodEndWC = now + minimumUpdatePeriod;
28627
+ var periodDuration = periodEndWC - periodStartWC;
28628
+ var segmentCount = Math.ceil(periodDuration * timescale / duration);
28629
+ var availableStart = Math.floor((now - periodStartWC - timeShiftBufferDepth) * timescale / duration);
28630
+ var availableEnd = Math.floor((now - periodStartWC) * timescale / duration);
28631
+
28632
+ return {
28633
+ start: Math.max(0, availableStart),
28634
+ end: Math.min(segmentCount, availableEnd)
28635
+ };
28636
+ }
28637
+ };
28638
+
28639
+ /**
28640
+ * Maps a range of numbers to objects with information needed to build the corresponding
28641
+ * segment list
28642
+ *
28643
+ * @name toSegmentsCallback
28644
+ * @function
28645
+ * @param {number} number
28646
+ * Number of the segment
28647
+ * @param {number} index
28648
+ * Index of the number in the range list
28649
+ * @return {{ number: Number, duration: Number, timeline: Number, time: Number }}
28650
+ * Object with segment timing and duration info
28651
+ */
28652
+
28653
+ /**
28654
+ * Returns a callback for Array.prototype.map for mapping a range of numbers to
28655
+ * information needed to build the segment list.
28656
+ *
28657
+ * @param {Object} attributes
28658
+ * Inherited MPD attributes
28659
+ * @return {toSegmentsCallback}
28660
+ * Callback map function
28661
+ */
28662
+ var toSegments = function toSegments(attributes) {
28663
+ return function (number, index) {
28664
+ var duration = attributes.duration,
28665
+ _attributes$timescale3 = attributes.timescale,
28666
+ timescale = _attributes$timescale3 === undefined ? 1 : _attributes$timescale3,
28667
+ periodIndex = attributes.periodIndex,
28668
+ _attributes$startNumb = attributes.startNumber,
28669
+ startNumber = _attributes$startNumb === undefined ? 1 : _attributes$startNumb;
28670
+
28671
+ return {
28672
+ number: startNumber + number,
28673
+ duration: duration / timescale,
28674
+ timeline: periodIndex,
28675
+ time: index * duration
28676
+ };
28677
+ };
28678
+ };
28679
+
28680
+ /**
28681
+ * Returns a list of objects containing segment timing and duration info used for
28682
+ * building the list of segments. This uses the @duration attribute specified
28683
+ * in the MPD manifest to derive the range of segments.
28684
+ *
28685
+ * @param {Object} attributes
28686
+ * Inherited MPD attributes
28687
+ * @return {{number: number, duration: number, time: number, timeline: number}[]}
28688
+ * List of Objects with segment timing and duration info
28689
+ */
28690
+ var parseByDuration = function parseByDuration(attributes) {
28691
+ var _attributes$type = attributes.type,
28692
+ type = _attributes$type === undefined ? 'static' : _attributes$type,
28693
+ duration = attributes.duration,
28694
+ _attributes$timescale4 = attributes.timescale,
28695
+ timescale = _attributes$timescale4 === undefined ? 1 : _attributes$timescale4,
28696
+ sourceDuration = attributes.sourceDuration;
28697
+
28698
+ var _segmentRange$type = segmentRange[type](attributes),
28699
+ start = _segmentRange$type.start,
28700
+ end = _segmentRange$type.end;
28701
+
28702
+ var segments = range(start, end).map(toSegments(attributes));
28703
+
28704
+ if (type === 'static') {
28705
+ var index = segments.length - 1;
28706
+
28707
+ // final segment may be less than full segment duration
28708
+ segments[index].duration = sourceDuration - duration / timescale * index;
28709
+ }
28710
+
28711
+ return segments;
28712
+ };
28713
+
28714
+ var identifierPattern = /\$([A-z]*)(?:(%0)([0-9]+)d)?\$/g;
28715
+
28716
+ /**
28717
+ * Replaces template identifiers with corresponding values. To be used as the callback
28718
+ * for String.prototype.replace
28719
+ *
28720
+ * @name replaceCallback
28721
+ * @function
28722
+ * @param {string} match
28723
+ * Entire match of identifier
28724
+ * @param {string} identifier
28725
+ * Name of matched identifier
28726
+ * @param {string} format
28727
+ * Format tag string. Its presence indicates that padding is expected
28728
+ * @param {string} width
28729
+ * Desired length of the replaced value. Values less than this width shall be left
28730
+ * zero padded
28731
+ * @return {string}
28732
+ * Replacement for the matched identifier
28733
+ */
28734
+
28735
+ /**
28736
+ * Returns a function to be used as a callback for String.prototype.replace to replace
28737
+ * template identifiers
28738
+ *
28739
+ * @param {Obect} values
28740
+ * Object containing values that shall be used to replace known identifiers
28741
+ * @param {number} values.RepresentationID
28742
+ * Value of the Representation@id attribute
28743
+ * @param {number} values.Number
28744
+ * Number of the corresponding segment
28745
+ * @param {number} values.Bandwidth
28746
+ * Value of the Representation@bandwidth attribute.
28747
+ * @param {number} values.Time
28748
+ * Timestamp value of the corresponding segment
28749
+ * @return {replaceCallback}
28750
+ * Callback to be used with String.prototype.replace to replace identifiers
28751
+ */
28752
+ var identifierReplacement = function identifierReplacement(values) {
28753
+ return function (match, identifier, format, width) {
28754
+ if (match === '$$') {
28755
+ // escape sequence
28756
+ return '$';
28757
+ }
28758
+
28759
+ if (typeof values[identifier] === 'undefined') {
28760
+ return match;
28761
+ }
28762
+
28763
+ var value = '' + values[identifier];
28764
+
28765
+ if (identifier === 'RepresentationID') {
28766
+ // Format tag shall not be present with RepresentationID
28767
+ return value;
28768
+ }
28769
+
28770
+ if (!format) {
28771
+ width = 1;
28772
+ } else {
28773
+ width = parseInt(width, 10);
28774
+ }
28775
+
28776
+ if (value.length >= width) {
28777
+ return value;
28778
+ }
28779
+
28780
+ return '' + new Array(width - value.length + 1).join('0') + value;
28781
+ };
28782
+ };
28783
+
28784
+ /**
28785
+ * Constructs a segment url from a template string
28786
+ *
28787
+ * @param {string} url
28788
+ * Template string to construct url from
28789
+ * @param {Obect} values
28790
+ * Object containing values that shall be used to replace known identifiers
28791
+ * @param {number} values.RepresentationID
28792
+ * Value of the Representation@id attribute
28793
+ * @param {number} values.Number
28794
+ * Number of the corresponding segment
28795
+ * @param {number} values.Bandwidth
28796
+ * Value of the Representation@bandwidth attribute.
28797
+ * @param {number} values.Time
28798
+ * Timestamp value of the corresponding segment
28799
+ * @return {string}
28800
+ * Segment url with identifiers replaced
28801
+ */
28802
+ var constructTemplateUrl = function constructTemplateUrl(url, values) {
28803
+ return url.replace(identifierPattern, identifierReplacement(values));
28804
+ };
28805
+
28806
+ /**
28807
+ * Generates a list of objects containing timing and duration information about each
28808
+ * segment needed to generate segment uris and the complete segment object
28809
+ *
28810
+ * @param {Object} attributes
28811
+ * Object containing all inherited attributes from parent elements with attribute
28812
+ * names as keys
28813
+ * @param {Object[]|undefined} segmentTimeline
28814
+ * List of objects representing the attributes of each S element contained within
28815
+ * the SegmentTimeline element
28816
+ * @return {{number: number, duration: number, time: number, timeline: number}[]}
28817
+ * List of Objects with segment timing and duration info
28818
+ */
28819
+ var parseTemplateInfo = function parseTemplateInfo(attributes, segmentTimeline) {
28820
+ if (!attributes.duration && !segmentTimeline) {
28821
+ // if neither @duration or SegmentTimeline are present, then there shall be exactly
28822
+ // one media segment
28823
+ return [{
28824
+ number: attributes.startNumber || 1,
28825
+ duration: attributes.sourceDuration,
28826
+ time: 0,
28827
+ timeline: attributes.periodIndex
28828
+ }];
28829
+ }
28830
+
28831
+ if (attributes.duration) {
28832
+ return parseByDuration(attributes);
28833
+ }
28834
+
28835
+ return parseByTimeline(attributes, segmentTimeline);
28836
+ };
28837
+
28838
+ /**
28839
+ * Generates a list of segments using information provided by the SegmentTemplate element
28840
+ *
28841
+ * @param {Object} attributes
28842
+ * Object containing all inherited attributes from parent elements with attribute
28843
+ * names as keys
28844
+ * @param {Object[]|undefined} segmentTimeline
28845
+ * List of objects representing the attributes of each S element contained within
28846
+ * the SegmentTimeline element
28847
+ * @return {Object[]}
28848
+ * List of segment objects
28849
+ */
28850
+ var segmentsFromTemplate = function segmentsFromTemplate(attributes, segmentTimeline) {
28851
+ var templateValues = {
28852
+ RepresentationID: attributes.id,
28853
+ Bandwidth: attributes.bandwidth || 0
28854
+ };
28855
+
28856
+ var _attributes$initializ = attributes.initialization,
28857
+ initialization = _attributes$initializ === undefined ? { sourceURL: '', range: '' } : _attributes$initializ;
28858
+
28859
+ var mapSegment = urlTypeToSegment({
28860
+ baseUrl: attributes.baseUrl,
28861
+ source: constructTemplateUrl(initialization.sourceURL, templateValues),
28862
+ range: initialization.range
28863
+ });
28864
+
28865
+ var segments = parseTemplateInfo(attributes, segmentTimeline);
28866
+
28867
+ return segments.map(function (segment) {
28868
+ templateValues.Number = segment.number;
28869
+ templateValues.Time = segment.time;
28870
+
28871
+ var uri = constructTemplateUrl(attributes.media || '', templateValues);
28872
+
28873
+ return {
28874
+ uri: uri,
28875
+ timeline: segment.timeline,
28876
+ duration: segment.duration,
28877
+ resolvedUri: resolveUrl(attributes.baseUrl || '', uri),
28878
+ map: mapSegment,
28879
+ number: segment.number
28880
+ };
28881
+ });
28882
+ };
28883
+
28884
+ var errors = {
28885
+ INVALID_NUMBER_OF_PERIOD: 'INVALID_NUMBER_OF_PERIOD',
28886
+ DASH_EMPTY_MANIFEST: 'DASH_EMPTY_MANIFEST',
28887
+ DASH_INVALID_XML: 'DASH_INVALID_XML',
28888
+ NO_BASE_URL: 'NO_BASE_URL',
28889
+ MISSING_SEGMENT_INFORMATION: 'MISSING_SEGMENT_INFORMATION',
28890
+ SEGMENT_TIME_UNSPECIFIED: 'SEGMENT_TIME_UNSPECIFIED',
28891
+ UNSUPPORTED_UTC_TIMING_SCHEME: 'UNSUPPORTED_UTC_TIMING_SCHEME'
28892
+ };
28893
+
28894
+ /**
28895
+ * Converts a <SegmentUrl> (of type URLType from the DASH spec 5.3.9.2 Table 14)
28896
+ * to an object that matches the output of a segment in videojs/mpd-parser
28897
+ *
28898
+ * @param {Object} attributes
28899
+ * Object containing all inherited attributes from parent elements with attribute
28900
+ * names as keys
28901
+ * @param {Object} segmentUrl
28902
+ * <SegmentURL> node to translate into a segment object
28903
+ * @return {Object} translated segment object
28904
+ */
28905
+ var SegmentURLToSegmentObject = function SegmentURLToSegmentObject(attributes, segmentUrl) {
28906
+ var baseUrl = attributes.baseUrl,
28907
+ _attributes$initializ = attributes.initialization,
28908
+ initialization = _attributes$initializ === undefined ? {} : _attributes$initializ;
28909
+
28910
+ var initSegment = urlTypeToSegment({
28911
+ baseUrl: baseUrl,
28912
+ source: initialization.sourceURL,
28913
+ range: initialization.range
28914
+ });
28915
+
28916
+ var segment = urlTypeToSegment({
28917
+ baseUrl: baseUrl,
28918
+ source: segmentUrl.media,
28919
+ range: segmentUrl.mediaRange
28920
+ });
28921
+
28922
+ segment.map = initSegment;
28923
+
28924
+ return segment;
28925
+ };
28926
+
28927
+ /**
28928
+ * Generates a list of segments using information provided by the SegmentList element
28929
+ * SegmentList (DASH SPEC Section 5.3.9.3.2) contains a set of <SegmentURL> nodes. Each
28930
+ * node should be translated into segment.
28931
+ *
28932
+ * @param {Object} attributes
28933
+ * Object containing all inherited attributes from parent elements with attribute
28934
+ * names as keys
28935
+ * @param {Object[]|undefined} segmentTimeline
28936
+ * List of objects representing the attributes of each S element contained within
28937
+ * the SegmentTimeline element
28938
+ * @return {Object.<Array>} list of segments
28939
+ */
28940
+ var segmentsFromList = function segmentsFromList(attributes, segmentTimeline) {
28941
+ var duration = attributes.duration,
28942
+ _attributes$segmentUr = attributes.segmentUrls,
28943
+ segmentUrls = _attributes$segmentUr === undefined ? [] : _attributes$segmentUr;
28944
+
28945
+ // Per spec (5.3.9.2.1) no way to determine segment duration OR
28946
+ // if both SegmentTimeline and @duration are defined, it is outside of spec.
28947
+
28948
+ if (!duration && !segmentTimeline || duration && segmentTimeline) {
28949
+ throw new Error(errors.SEGMENT_TIME_UNSPECIFIED);
28950
+ }
28951
+
28952
+ var segmentUrlMap = segmentUrls.map(function (segmentUrlObject) {
28953
+ return SegmentURLToSegmentObject(attributes, segmentUrlObject);
28954
+ });
28955
+ var segmentTimeInfo = void 0;
28956
+
28957
+ if (duration) {
28958
+ segmentTimeInfo = parseByDuration(attributes);
28959
+ }
28960
+
28961
+ if (segmentTimeline) {
28962
+ segmentTimeInfo = parseByTimeline(attributes, segmentTimeline);
28963
+ }
28964
+
28965
+ var segments = segmentTimeInfo.map(function (segmentTime, index) {
28966
+ if (segmentUrlMap[index]) {
28967
+ var segment = segmentUrlMap[index];
28968
+
28969
+ segment.timeline = segmentTime.timeline;
28970
+ segment.duration = segmentTime.duration;
28971
+ segment.number = segmentTime.number;
28972
+ return segment;
28973
+ }
28974
+ // Since we're mapping we should get rid of any blank segments (in case
28975
+ // the given SegmentTimeline is handling for more elements than we have
28976
+ // SegmentURLs for).
28977
+ }).filter(function (segment) {
28978
+ return segment;
28979
+ });
28980
+
28981
+ return segments;
28982
+ };
28983
+
28984
+ /**
28985
+ * Translates SegmentBase into a set of segments.
28986
+ * (DASH SPEC Section 5.3.9.3.2) contains a set of <SegmentURL> nodes. Each
28987
+ * node should be translated into segment.
28988
+ *
28989
+ * @param {Object} attributes
28990
+ * Object containing all inherited attributes from parent elements with attribute
28991
+ * names as keys
28992
+ * @return {Object.<Array>} list of segments
28993
+ */
28994
+ var segmentsFromBase = function segmentsFromBase(attributes) {
28995
+ var baseUrl = attributes.baseUrl,
28996
+ _attributes$initializ = attributes.initialization,
28997
+ initialization = _attributes$initializ === undefined ? {} : _attributes$initializ,
28998
+ sourceDuration = attributes.sourceDuration,
28999
+ _attributes$timescale = attributes.timescale,
29000
+ timescale = _attributes$timescale === undefined ? 1 : _attributes$timescale,
29001
+ _attributes$indexRang = attributes.indexRange,
29002
+ indexRange = _attributes$indexRang === undefined ? '' : _attributes$indexRang,
29003
+ duration = attributes.duration;
29004
+
29005
+ // base url is required for SegmentBase to work, per spec (Section 5.3.9.2.1)
29006
+
29007
+ if (!baseUrl) {
29008
+ throw new Error(errors.NO_BASE_URL);
29009
+ }
29010
+
29011
+ var initSegment = urlTypeToSegment({
29012
+ baseUrl: baseUrl,
29013
+ source: initialization.sourceURL,
29014
+ range: initialization.range
29015
+ });
29016
+ var segment = urlTypeToSegment({ baseUrl: baseUrl, source: baseUrl, range: indexRange });
29017
+
29018
+ segment.map = initSegment;
29019
+
29020
+ // If there is a duration, use it, otherwise use the given duration of the source
29021
+ // (since SegmentBase is only for one total segment)
29022
+ if (duration) {
29023
+ var segmentTimeInfo = parseByDuration(attributes);
29024
+
29025
+ if (segmentTimeInfo.length) {
29026
+ segment.duration = segmentTimeInfo[0].duration;
29027
+ segment.timeline = segmentTimeInfo[0].timeline;
29028
+ }
29029
+ } else if (sourceDuration) {
29030
+ segment.duration = sourceDuration / timescale;
29031
+ segment.timeline = 0;
29032
+ }
29033
+
29034
+ // This is used for mediaSequence
29035
+ segment.number = 0;
29036
+
29037
+ return [segment];
29038
+ };
29039
+
29040
+ var generateSegments = function generateSegments(_ref) {
29041
+ var attributes = _ref.attributes,
29042
+ segmentInfo = _ref.segmentInfo;
29043
+
29044
+ var segmentAttributes = void 0;
29045
+ var segmentsFn = void 0;
29046
+
29047
+ if (segmentInfo.template) {
29048
+ segmentsFn = segmentsFromTemplate;
29049
+ segmentAttributes = merge(attributes, segmentInfo.template);
29050
+ } else if (segmentInfo.base) {
29051
+ segmentsFn = segmentsFromBase;
29052
+ segmentAttributes = merge(attributes, segmentInfo.base);
29053
+ } else if (segmentInfo.list) {
29054
+ segmentsFn = segmentsFromList;
29055
+ segmentAttributes = merge(attributes, segmentInfo.list);
29056
+ }
29057
+
29058
+ if (!segmentsFn) {
29059
+ return { attributes: attributes };
29060
+ }
29061
+
29062
+ var segments = segmentsFn(segmentAttributes, segmentInfo.timeline);
29063
+
29064
+ // The @duration attribute will be used to determin the playlist's targetDuration which
29065
+ // must be in seconds. Since we've generated the segment list, we no longer need
29066
+ // @duration to be in @timescale units, so we can convert it here.
29067
+ if (segmentAttributes.duration) {
29068
+ var _segmentAttributes = segmentAttributes,
29069
+ duration = _segmentAttributes.duration,
29070
+ _segmentAttributes$ti = _segmentAttributes.timescale,
29071
+ timescale = _segmentAttributes$ti === undefined ? 1 : _segmentAttributes$ti;
29072
+
29073
+ segmentAttributes.duration = duration / timescale;
29074
+ } else if (segments.length) {
29075
+ // if there is no @duration attribute, use the largest segment duration as
29076
+ // as target duration
29077
+ segmentAttributes.duration = segments.reduce(function (max, segment) {
29078
+ return Math.max(max, Math.ceil(segment.duration));
29079
+ }, 0);
29080
+ } else {
29081
+ segmentAttributes.duration = 0;
29082
+ }
29083
+
29084
+ return {
29085
+ attributes: segmentAttributes,
29086
+ segments: segments
29087
+ };
29088
+ };
29089
+
29090
+ var toPlaylists = function toPlaylists(representations) {
29091
+ return representations.map(generateSegments);
29092
+ };
29093
+
29094
+ var findChildren = function findChildren(element, name) {
29095
+ return from(element.childNodes).filter(function (_ref) {
29096
+ var tagName = _ref.tagName;
29097
+ return tagName === name;
29098
+ });
29099
+ };
29100
+
29101
+ var getContent = function getContent(element) {
29102
+ return element.textContent.trim();
29103
+ };
29104
+
29105
+ var parseDuration = function parseDuration(str) {
29106
+ var SECONDS_IN_YEAR = 365 * 24 * 60 * 60;
29107
+ var SECONDS_IN_MONTH = 30 * 24 * 60 * 60;
29108
+ var SECONDS_IN_DAY = 24 * 60 * 60;
29109
+ var SECONDS_IN_HOUR = 60 * 60;
29110
+ var SECONDS_IN_MIN = 60;
29111
+
29112
+ // P10Y10M10DT10H10M10.1S
29113
+ var durationRegex = /P(?:(\d*)Y)?(?:(\d*)M)?(?:(\d*)D)?(?:T(?:(\d*)H)?(?:(\d*)M)?(?:([\d.]*)S)?)?/;
29114
+ var match = durationRegex.exec(str);
29115
+
29116
+ if (!match) {
29117
+ return 0;
29118
+ }
29119
+
29120
+ var _match$slice = match.slice(1),
29121
+ year = _match$slice[0],
29122
+ month = _match$slice[1],
29123
+ day = _match$slice[2],
29124
+ hour = _match$slice[3],
29125
+ minute = _match$slice[4],
29126
+ second = _match$slice[5];
29127
+
29128
+ return parseFloat(year || 0) * SECONDS_IN_YEAR + parseFloat(month || 0) * SECONDS_IN_MONTH + parseFloat(day || 0) * SECONDS_IN_DAY + parseFloat(hour || 0) * SECONDS_IN_HOUR + parseFloat(minute || 0) * SECONDS_IN_MIN + parseFloat(second || 0);
29129
+ };
29130
+
29131
+ var parseDate = function parseDate(str) {
29132
+ // Date format without timezone according to ISO 8601
29133
+ // YYY-MM-DDThh:mm:ss.ssssss
29134
+ var dateRegex = /^\d+-\d+-\d+T\d+:\d+:\d+(\.\d+)?$/;
29135
+
29136
+ // If the date string does not specifiy a timezone, we must specifiy UTC. This is
29137
+ // expressed by ending with 'Z'
29138
+ if (dateRegex.test(str)) {
29139
+ str += 'Z';
29140
+ }
29141
+
29142
+ return Date.parse(str);
29143
+ };
29144
+
29145
+ // TODO: maybe order these in some way that makes it easy to find specific attributes
29146
+ var parsers = {
29147
+ /**
29148
+ * Specifies the duration of the entire Media Presentation. Format is a duration string
29149
+ * as specified in ISO 8601
29150
+ *
29151
+ * @param {string} value
29152
+ * value of attribute as a string
29153
+ * @return {number}
29154
+ * The duration in seconds
29155
+ */
29156
+ mediaPresentationDuration: function mediaPresentationDuration(value) {
29157
+ return parseDuration(value);
29158
+ },
29159
+
29160
+ /**
29161
+ * Specifies the Segment availability start time for all Segments referred to in this
29162
+ * MPD. For a dynamic manifest, it specifies the anchor for the earliest availability
29163
+ * time. Format is a date string as specified in ISO 8601
29164
+ *
29165
+ * @param {string} value
29166
+ * value of attribute as a string
29167
+ * @return {number}
29168
+ * The date as seconds from unix epoch
29169
+ */
29170
+ availabilityStartTime: function availabilityStartTime(value) {
29171
+ return parseDate(value) / 1000;
29172
+ },
29173
+
29174
+ /**
29175
+ * Specifies the smallest period between potential changes to the MPD. Format is a
29176
+ * duration string as specified in ISO 8601
29177
+ *
29178
+ * @param {string} value
29179
+ * value of attribute as a string
29180
+ * @return {number}
29181
+ * The duration in seconds
29182
+ */
29183
+ minimumUpdatePeriod: function minimumUpdatePeriod(value) {
29184
+ return parseDuration(value);
29185
+ },
29186
+
29187
+ /**
29188
+ * Specifies the duration of the smallest time shifting buffer for any Representation
29189
+ * in the MPD. Format is a duration string as specified in ISO 8601
29190
+ *
29191
+ * @param {string} value
29192
+ * value of attribute as a string
29193
+ * @return {number}
29194
+ * The duration in seconds
29195
+ */
29196
+ timeShiftBufferDepth: function timeShiftBufferDepth(value) {
29197
+ return parseDuration(value);
29198
+ },
29199
+
29200
+ /**
29201
+ * Specifies the PeriodStart time of the Period relative to the availabilityStarttime.
29202
+ * Format is a duration string as specified in ISO 8601
29203
+ *
29204
+ * @param {string} value
29205
+ * value of attribute as a string
29206
+ * @return {number}
29207
+ * The duration in seconds
29208
+ */
29209
+ start: function start(value) {
29210
+ return parseDuration(value);
29211
+ },
29212
+
29213
+ /**
29214
+ * Specifies the width of the visual presentation
29215
+ *
29216
+ * @param {string} value
29217
+ * value of attribute as a string
29218
+ * @return {number}
29219
+ * The parsed width
29220
+ */
29221
+ width: function width(value) {
29222
+ return parseInt(value, 10);
29223
+ },
29224
+
29225
+ /**
29226
+ * Specifies the height of the visual presentation
29227
+ *
29228
+ * @param {string} value
29229
+ * value of attribute as a string
29230
+ * @return {number}
29231
+ * The parsed height
29232
+ */
29233
+ height: function height(value) {
29234
+ return parseInt(value, 10);
29235
+ },
29236
+
29237
+ /**
29238
+ * Specifies the bitrate of the representation
29239
+ *
29240
+ * @param {string} value
29241
+ * value of attribute as a string
29242
+ * @return {number}
29243
+ * The parsed bandwidth
29244
+ */
29245
+ bandwidth: function bandwidth(value) {
29246
+ return parseInt(value, 10);
29247
+ },
29248
+
29249
+ /**
29250
+ * Specifies the number of the first Media Segment in this Representation in the Period
29251
+ *
29252
+ * @param {string} value
29253
+ * value of attribute as a string
29254
+ * @return {number}
29255
+ * The parsed number
29256
+ */
29257
+ startNumber: function startNumber(value) {
29258
+ return parseInt(value, 10);
29259
+ },
29260
+
29261
+ /**
29262
+ * Specifies the timescale in units per seconds
29263
+ *
29264
+ * @param {string} value
29265
+ * value of attribute as a string
29266
+ * @return {number}
29267
+ * The aprsed timescale
29268
+ */
29269
+ timescale: function timescale(value) {
29270
+ return parseInt(value, 10);
29271
+ },
29272
+
29273
+ /**
29274
+ * Specifies the constant approximate Segment duration
29275
+ * NOTE: The <Period> element also contains an @duration attribute. This duration
29276
+ * specifies the duration of the Period. This attr