OLD | NEW |
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 'use strict'; | 5 'use strict'; |
6 | 6 |
7 /** | 7 /** |
8 * @fileoverview MediaControls class implements media playback controls | 8 * @fileoverview MediaControls class implements media playback controls |
9 * that exist outside of the audio/video HTML element. | 9 * that exist outside of the audio/video HTML element. |
10 */ | 10 */ |
(...skipping 147 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
158 this.pause(); | 158 this.pause(); |
159 else | 159 else |
160 this.play(); | 160 this.play(); |
161 }; | 161 }; |
162 | 162 |
163 /** | 163 /** |
164 * @param {HTMLElement=} opt_parent Parent container. | 164 * @param {HTMLElement=} opt_parent Parent container. |
165 */ | 165 */ |
166 MediaControls.prototype.initPlayButton = function(opt_parent) { | 166 MediaControls.prototype.initPlayButton = function(opt_parent) { |
167 this.playButton_ = this.createButton('play media-control', | 167 this.playButton_ = this.createButton('play media-control', |
168 this.togglePlayState.bind(this), opt_parent, 3 /* States. */); | 168 this.onPlayButtonClicked.bind(this), opt_parent, 3 /* States. */); |
169 }; | 169 }; |
170 | 170 |
171 /* | 171 /* |
172 * Time controls | 172 * Time controls |
173 */ | 173 */ |
174 | 174 |
175 /** | 175 /** |
176 * The default range of 100 is too coarse for the media progress slider. | 176 * The default range of 100 is too coarse for the media progress slider. |
177 */ | 177 */ |
178 MediaControls.PROGRESS_RANGE = 5000; | 178 MediaControls.PROGRESS_RANGE = 5000; |
(...skipping 775 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
954 this.getValue(), MediaControls.PreciseSlider.HIDE_AFTER_DRAG_DELAY); | 954 this.getValue(), MediaControls.PreciseSlider.HIDE_AFTER_DRAG_DELAY); |
955 this.latestMouseUpTime_ = Date.now(); | 955 this.latestMouseUpTime_ = Date.now(); |
956 } | 956 } |
957 }; | 957 }; |
958 | 958 |
959 /** | 959 /** |
960 * Create video controls. | 960 * Create video controls. |
961 * | 961 * |
962 * @param {HTMLElement} containerElement The container for the controls. | 962 * @param {HTMLElement} containerElement The container for the controls. |
963 * @param {function} onMediaError Function to display an error message. | 963 * @param {function} onMediaError Function to display an error message. |
| 964 * @param {function(string):string} stringFunction Function providing localized |
| 965 * strings. |
964 * @param {function=} opt_fullScreenToggle Function to toggle fullscreen mode. | 966 * @param {function=} opt_fullScreenToggle Function to toggle fullscreen mode. |
965 * @param {HTMLElement=} opt_stateIconParent The parent for the icon that | 967 * @param {HTMLElement=} opt_stateIconParent The parent for the icon that |
966 * gives visual feedback when the playback state changes. | 968 * gives visual feedback when the playback state changes. |
967 * @constructor | 969 * @constructor |
968 */ | 970 */ |
969 function VideoControls(containerElement, onMediaError, | 971 function VideoControls(containerElement, onMediaError, stringFunction, |
970 opt_fullScreenToggle, opt_stateIconParent) { | 972 opt_fullScreenToggle, opt_stateIconParent) { |
971 MediaControls.call(this, containerElement, onMediaError); | 973 MediaControls.call(this, containerElement, onMediaError); |
| 974 this.stringFunction_ = stringFunction; |
972 | 975 |
973 this.container_.classList.add('video-controls'); | 976 this.container_.classList.add('video-controls'); |
974 | |
975 this.initPlayButton(); | 977 this.initPlayButton(); |
976 | |
977 this.initTimeControls(true /* show seek mark */); | 978 this.initTimeControls(true /* show seek mark */); |
978 | |
979 this.initVolumeControls(); | 979 this.initVolumeControls(); |
980 | 980 |
981 if (opt_fullScreenToggle) { | 981 if (opt_fullScreenToggle) { |
982 this.fullscreenButton_ = | 982 this.fullscreenButton_ = |
983 this.createButton('fullscreen', opt_fullScreenToggle); | 983 this.createButton('fullscreen', opt_fullScreenToggle); |
984 } | 984 } |
985 | 985 |
986 if (opt_stateIconParent) { | 986 if (opt_stateIconParent) { |
987 this.stateIcon_ = this.createControl( | 987 this.stateIcon_ = this.createControl( |
988 'playback-state-icon', opt_stateIconParent); | 988 'playback-state-icon', opt_stateIconParent); |
| 989 this.textBanner_ = this.createControl('text-banner', opt_stateIconParent); |
989 } | 990 } |
990 | 991 |
991 var videoControls = this; | 992 var videoControls = this; |
992 chrome.mediaPlayerPrivate.onTogglePlayState.addListener( | 993 chrome.mediaPlayerPrivate.onTogglePlayState.addListener( |
993 function() { videoControls.togglePlayStateWithFeedback(); }); | 994 function() { videoControls.togglePlayStateWithFeedback(); }); |
994 } | 995 } |
995 | 996 |
996 /** | 997 /** |
997 * No resume if we are withing this margin from the start or the end. | 998 * No resume if we are withing this margin from the start or the end. |
998 */ | 999 */ |
999 VideoControls.RESUME_MARGIN = 0.03; | 1000 VideoControls.RESUME_MARGIN = 0.03; |
1000 | 1001 |
1001 /** | 1002 /** |
1002 * No resume for videos shorter than this. | 1003 * No resume for videos shorter than this. |
1003 */ | 1004 */ |
1004 VideoControls.RESUME_THRESHOLD = 5 * 60; // 5 min. | 1005 VideoControls.RESUME_THRESHOLD = 5 * 60; // 5 min. |
1005 | 1006 |
1006 /** | 1007 /** |
1007 * When resuming rewind back this much. | 1008 * When resuming rewind back this much. |
1008 */ | 1009 */ |
1009 VideoControls.RESUME_REWIND = 5; // seconds. | 1010 VideoControls.RESUME_REWIND = 5; // seconds. |
1010 | 1011 |
1011 VideoControls.prototype = { __proto__: MediaControls.prototype }; | 1012 VideoControls.prototype = { __proto__: MediaControls.prototype }; |
1012 | 1013 |
1013 /** | 1014 /** |
| 1015 * Shows icon feedback for the current state of the video player. |
| 1016 * @private |
| 1017 */ |
| 1018 VideoControls.prototype.showIconFeedback_ = function() { |
| 1019 this.stateIcon_.removeAttribute('state'); |
| 1020 setTimeout(function() { |
| 1021 this.stateIcon_.setAttribute('state', this.isPlaying() ? 'play' : 'pause'); |
| 1022 }.bind(this), 0); |
| 1023 }; |
| 1024 |
| 1025 /** |
| 1026 * Shows a text banner. |
| 1027 * |
| 1028 * @param {string} identifier String identifier. |
| 1029 * @private |
| 1030 */ |
| 1031 VideoControls.prototype.showTextBanner_ = function(identifier) { |
| 1032 this.textBanner_.removeAttribute('visible'); |
| 1033 this.textBanner_.textContent = this.stringFunction_(identifier); |
| 1034 setTimeout(function() { |
| 1035 this.textBanner_.setAttribute('visible', 'true'); |
| 1036 }.bind(this), 0); |
| 1037 }; |
| 1038 |
| 1039 /** |
| 1040 * Toggle play/pause state on a mouse click on the play/pause button. Can be |
| 1041 * called externally. |
| 1042 * |
| 1043 * @param {Event} event Mouse click event. |
| 1044 */ |
| 1045 VideoControls.prototype.onPlayButtonClicked = function(event) { |
| 1046 if (event.ctrlKey) { |
| 1047 this.toggleLoopedModeWithFeedback(true); |
| 1048 if (!this.isPlaying()) |
| 1049 this.togglePlayState(); |
| 1050 } else { |
| 1051 this.togglePlayState(); |
| 1052 } |
| 1053 }; |
| 1054 |
| 1055 /** |
1014 * Media completion handler. | 1056 * Media completion handler. |
1015 */ | 1057 */ |
1016 VideoControls.prototype.onMediaComplete = function() { | 1058 VideoControls.prototype.onMediaComplete = function() { |
1017 this.onMediaPlay_(false); // Just update the UI. | 1059 this.onMediaPlay_(false); // Just update the UI. |
1018 this.savePosition(); // This will effectively forget the position. | 1060 this.savePosition(); // This will effectively forget the position. |
1019 }; | 1061 }; |
1020 | 1062 |
1021 /** | 1063 /** |
1022 * Toggle play/pause state and flash an icon over the video. | 1064 * Toggles the looped mode with feedback. |
| 1065 * @param {boolean} on Whether enabled or not. |
| 1066 */ |
| 1067 VideoControls.prototype.toggleLoopedModeWithFeedback = function(on) { |
| 1068 if (!this.getMedia().duration) |
| 1069 return; |
| 1070 |
| 1071 this.toggleLoopedMode(on); |
| 1072 if (on) |
| 1073 this.showTextBanner_('GALLERY_VIDEO_LOOPED_MODE'); |
| 1074 }; |
| 1075 |
| 1076 /** |
| 1077 * Toggles the looped mode. |
| 1078 * @param {boolean} on Whether enabled or not. |
| 1079 */ |
| 1080 VideoControls.prototype.toggleLoopedMode = function(on) { |
| 1081 this.getMedia().loop = on; |
| 1082 }; |
| 1083 |
| 1084 /** |
| 1085 * Toggles play/pause state and flash an icon over the video. |
1023 */ | 1086 */ |
1024 VideoControls.prototype.togglePlayStateWithFeedback = function() { | 1087 VideoControls.prototype.togglePlayStateWithFeedback = function() { |
1025 if (!this.getMedia().duration) | 1088 if (!this.getMedia().duration) |
1026 return; | 1089 return; |
1027 | 1090 |
1028 this.togglePlayState(); | 1091 this.togglePlayState(); |
1029 | 1092 this.showIconFeedback_(); |
1030 this.stateIcon_.removeAttribute('state'); | |
1031 setTimeout(function() { | |
1032 this.stateIcon_.setAttribute('state', this.isPlaying() ? 'play' : 'pause'); | |
1033 }.bind(this), 0); | |
1034 }; | 1093 }; |
1035 | 1094 |
1036 /** | 1095 /** |
1037 * Toggle play/pause state. | 1096 * Toggles play/pause state. |
1038 */ | 1097 */ |
1039 VideoControls.prototype.togglePlayState = function() { | 1098 VideoControls.prototype.togglePlayState = function() { |
1040 if (this.isPlaying()) { | 1099 if (this.isPlaying()) { |
1041 // User gave the Pause command. | 1100 // User gave the Pause command. Save the state and reset the loop mode. |
| 1101 this.toggleLoopedMode(false); |
1042 this.savePosition(); | 1102 this.savePosition(); |
1043 } | 1103 } |
1044 MediaControls.prototype.togglePlayState.apply(this, arguments); | 1104 MediaControls.prototype.togglePlayState.apply(this, arguments); |
1045 }; | 1105 }; |
1046 | 1106 |
1047 /** | 1107 /** |
1048 * Save the playback position to the persistent storage. | 1108 * Saves the playback position to the persistent storage. |
1049 * @param {boolean=} opt_sync True if the position must be saved synchronously | 1109 * @param {boolean=} opt_sync True if the position must be saved synchronously |
1050 * (required when closing app windows). | 1110 * (required when closing app windows). |
1051 */ | 1111 */ |
1052 VideoControls.prototype.savePosition = function(opt_sync) { | 1112 VideoControls.prototype.savePosition = function(opt_sync) { |
1053 if (!this.media_.duration || | 1113 if (!this.media_.duration || |
1054 this.media_.duration < VideoControls.RESUME_THRESHOLD) { | 1114 this.media_.duration < VideoControls.RESUME_THRESHOLD) { |
1055 return; | 1115 return; |
1056 } | 1116 } |
1057 | 1117 |
1058 var ratio = this.media_.currentTime / this.media_.duration; | 1118 var ratio = this.media_.currentTime / this.media_.duration; |
(...skipping 13 matching lines...) Expand all Loading... |
1072 // Pass the data to the background page. | 1132 // Pass the data to the background page. |
1073 if (!window.saveOnExit) | 1133 if (!window.saveOnExit) |
1074 window.saveOnExit = []; | 1134 window.saveOnExit = []; |
1075 window.saveOnExit.push({ key: this.media_.src, value: position }); | 1135 window.saveOnExit.push({ key: this.media_.src, value: position }); |
1076 } else { | 1136 } else { |
1077 util.AppCache.update(this.media_.src, position); | 1137 util.AppCache.update(this.media_.src, position); |
1078 } | 1138 } |
1079 }; | 1139 }; |
1080 | 1140 |
1081 /** | 1141 /** |
1082 * Resume the playback position saved in the persistent storage. | 1142 * Resumes the playback position saved in the persistent storage. |
1083 */ | 1143 */ |
1084 VideoControls.prototype.restorePlayState = function() { | 1144 VideoControls.prototype.restorePlayState = function() { |
1085 if (this.media_.duration >= VideoControls.RESUME_THRESHOLD) { | 1145 if (this.media_.duration >= VideoControls.RESUME_THRESHOLD) { |
1086 util.AppCache.getValue(this.media_.src, function(position) { | 1146 util.AppCache.getValue(this.media_.src, function(position) { |
1087 if (position) | 1147 if (position) |
1088 this.media_.currentTime = position; | 1148 this.media_.currentTime = position; |
1089 }.bind(this)); | 1149 }.bind(this)); |
1090 } | 1150 } |
1091 }; | 1151 }; |
1092 | 1152 |
1093 /** | 1153 /** |
1094 * Update style to best fit the size of the container. | 1154 * Updates style to best fit the size of the container. |
1095 */ | 1155 */ |
1096 VideoControls.prototype.updateStyle = function() { | 1156 VideoControls.prototype.updateStyle = function() { |
1097 // We assume that the video controls element fills the parent container. | 1157 // We assume that the video controls element fills the parent container. |
1098 // This is easier than adding margins to this.container_.clientWidth. | 1158 // This is easier than adding margins to this.container_.clientWidth. |
1099 var width = this.container_.parentNode.clientWidth; | 1159 var width = this.container_.parentNode.clientWidth; |
1100 | 1160 |
1101 // Set the margin to 5px for width >= 400, 0px for width < 160, | 1161 // Set the margin to 5px for width >= 400, 0px for width < 160, |
1102 // interpolate linearly in between. | 1162 // interpolate linearly in between. |
1103 this.container_.style.margin = | 1163 this.container_.style.margin = |
1104 Math.ceil((Math.max(160, Math.min(width, 400)) - 160) / 48) + 'px'; | 1164 Math.ceil((Math.max(160, Math.min(width, 400)) - 160) / 48) + 'px'; |
1105 | 1165 |
1106 var hideBelow = function(selector, limit) { | 1166 var hideBelow = function(selector, limit) { |
1107 this.container_.querySelector(selector).style.display = | 1167 this.container_.querySelector(selector).style.display = |
1108 width < limit ? 'none' : '-webkit-box'; | 1168 width < limit ? 'none' : '-webkit-box'; |
1109 }.bind(this); | 1169 }.bind(this); |
1110 | 1170 |
1111 hideBelow('.time', 350); | 1171 hideBelow('.time', 350); |
1112 hideBelow('.volume', 275); | 1172 hideBelow('.volume', 275); |
1113 hideBelow('.volume-controls', 210); | 1173 hideBelow('.volume-controls', 210); |
1114 hideBelow('.fullscreen', 150); | 1174 hideBelow('.fullscreen', 150); |
1115 }; | 1175 }; |
1116 | 1176 |
1117 /** | 1177 /** |
1118 * Create audio controls. | 1178 * Creates audio controls. |
1119 * | 1179 * |
1120 * @param {HTMLElement} container Parent container. | 1180 * @param {HTMLElement} container Parent container. |
1121 * @param {function(boolean)} advanceTrack Parameter: true=forward. | 1181 * @param {function(boolean)} advanceTrack Parameter: true=forward. |
1122 * @param {function} onError Error handler. | 1182 * @param {function} onError Error handler. |
1123 * @constructor | 1183 * @constructor |
1124 */ | 1184 */ |
1125 function AudioControls(container, advanceTrack, onError) { | 1185 function AudioControls(container, advanceTrack, onError) { |
1126 MediaControls.call(this, container, onError); | 1186 MediaControls.call(this, container, onError); |
1127 | 1187 |
1128 this.container_.classList.add('audio-controls'); | 1188 this.container_.classList.add('audio-controls'); |
(...skipping 36 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1165 AudioControls.prototype.onAdvanceClick_ = function(forward) { | 1225 AudioControls.prototype.onAdvanceClick_ = function(forward) { |
1166 if (!forward && | 1226 if (!forward && |
1167 (this.getMedia().currentTime > AudioControls.TRACK_RESTART_THRESHOLD)) { | 1227 (this.getMedia().currentTime > AudioControls.TRACK_RESTART_THRESHOLD)) { |
1168 // We are far enough from the beginning of the current track. | 1228 // We are far enough from the beginning of the current track. |
1169 // Restart it instead of than skipping to the previous one. | 1229 // Restart it instead of than skipping to the previous one. |
1170 this.getMedia().currentTime = 0; | 1230 this.getMedia().currentTime = 0; |
1171 } else { | 1231 } else { |
1172 this.advanceTrack_(forward); | 1232 this.advanceTrack_(forward); |
1173 } | 1233 } |
1174 }; | 1234 }; |
OLD | NEW |