Commit 5c8f889b authored by fukino's avatar fukino Committed by Commit bot

AudioPlayer: Update control panel with playback progress bar.

This CL includes:
- Replace custom progress bar with paper-slider.
- Pause track temporalily while dragging progress bar to be consistent with video player.
- Adjust layout of control panel.
- Show playing time as "{current} / {duration}".

BUG=488229
TEST=manually

Review URL: https://codereview.chromium.org/1403303011

Cr-Commit-Position: refs/heads/master@{#357064}
parent 0e50da17
...@@ -5,7 +5,6 @@ ...@@ -5,7 +5,6 @@
control-panel { control-panel {
bottom: 0; bottom: 0;
left: 0; left: 0;
margin-bottom: 8px;
position: fixed; position: fixed;
right: 0; right: 0;
} }
...@@ -93,6 +93,12 @@ Polymer({ ...@@ -93,6 +93,12 @@ Polymer({
} }
}, },
/**
* The last playing state when user starts dragging the seek bar.
* @private {boolean}
*/
wasPlayingOnDragStart_: false,
/** /**
* Handles change event for shuffle mode. * Handles change event for shuffle mode.
* @param {boolean} shuffle * @param {boolean} shuffle
...@@ -135,6 +141,9 @@ Polymer({ ...@@ -135,6 +141,9 @@ Polymer({
ready: function() { ready: function() {
this.addEventListener('keydown', this.onKeyDown_.bind(this)); this.addEventListener('keydown', this.onKeyDown_.bind(this));
this.$.audioController.addEventListener('dragging-changed',
this.onDraggingChanged_.bind(this));
this.$.audio.volume = 0; // Temporary initial volume. this.$.audio.volume = 0; // Temporary initial volume.
this.$.audio.addEventListener('ended', this.onAudioEnded.bind(this)); this.$.audio.addEventListener('ended', this.onAudioEnded.bind(this));
this.$.audio.addEventListener('error', this.onAudioError.bind(this)); this.$.audio.addEventListener('error', this.onAudioError.bind(this));
...@@ -395,6 +404,24 @@ Polymer({ ...@@ -395,6 +404,24 @@ Polymer({
this.$.audio.src = ''; // Hack to prevent crashing. this.$.audio.src = ''; // Hack to prevent crashing.
}, },
/**
* Invoked when dragging state of seek bar on control panel is changed.
* During the user is dragging it, audio playback is paused temporalily.
*/
onDraggingChanged_: function() {
if (this.$.audioController.dragging) {
if (this.playing) {
this.wasPlayingOnDragStart_ = true;
this.$.audio.pause();
}
} else {
if (this.wasPlayingOnDragStart_) {
this.$.audio.play();
this.wasPlayingOnDragStart_ = false;
}
}
},
/** /**
* Invoked when the 'keydown' event is fired. * Invoked when the 'keydown' event is fired.
* @param {Event} event The event object. * @param {Event} event The event object.
......
...@@ -8,19 +8,17 @@ ...@@ -8,19 +8,17 @@
background-color: white; background-color: white;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
height: 64px; height: 96px;
justify-content: center; justify-content: center;
padding: 0; padding: 0;
} }
.controls .upper-controls { .controls .upper-controls,
height: 32px;
width: 100%
}
.controls .lower-controls { .controls .lower-controls {
height: 32px; box-sizing: border-box;
width: 100% height: 48px;
padding: 8px;
width: 100%;
} }
.audio-controls { .audio-controls {
...@@ -82,173 +80,40 @@ ...@@ -82,173 +80,40 @@
justify-content: center; justify-content: center;
} }
.custom-slider.progress { .time-controls > .time-container {
display: flex; color: rgb(51, 51, 51);
flex: 1 1 auto;
height: 100%;
position: relative;
z-index: 0; /* Make a layer which includes the thumb on slider. */
}
.custom-slider.progress > input[type='range']::-webkit-slider-thumb {
background-image: -webkit-image-set(
url(../assets/100/player_timeline_handler.png) 1x,
url(../assets/200/player_timeline_handler.png) 2x);
width: 28px;
}
.custom-slider.progress > input[type='range']::-webkit-slider-thumb:hover {
background-image: -webkit-image-set(
url(../assets/100/player_timeline_handler.png) 1x,
url(../assets/200/player_timeline_handler.png) 2x);
}
.custom-slider.progress > input[type='range']::-webkit-slider-thumb:active {
background-image: -webkit-image-set(
url(../assets/100/player_timeline_handler_pressed.png) 1x,
url(../assets/200/player_timeline_handler_pressed.png) 2x);
}
.custom-slider.progress.disabled > input[type='range']::-webkit-slider-thumb {
background-image: none;
}
.time-controls > .time {
cursor: default; cursor: default;
height: 100%; flex: none;
position: relative; font-size: 12px;
width: 53px; padding: 8px;
}
.time-controls > .time.disabled {
opacity: 0;
}
.custom-slider > input[type='range'] {
-webkit-appearance: none !important; /* Hide the default thumb icon. */
background: transparent; /* Hide the standard slider bar */
height: 100%;
left: -2px; /* Required to align the input element with the parent. */
outline: none;
position: absolute;
top: -2px;
width: 100%;
}
/* Custom thumb icon. */
.custom-slider > input[type='range']::-webkit-slider-thumb {
-webkit-appearance: none;
background-position: center center;
background-repeat: no-repeat;
height: 24px;
position: relative; position: relative;
z-index: 2;
}
/* Custom slider bar (we hide the standard one). */
.custom-slider > .bar {
background-image: -webkit-image-set(
url(../assets/100/player_timeline_base_center.png) 1x,
url(../assets/200/player_timeline_base_center.png) 2x);
/* In order to match the horizontal position of the standard slider bar
left and right must be equal to 1/2 of the thumb icon width. */
bottom: 15px;
left: 14px; /* Exactly 1/2 of the thumb width */
pointer-events: none; /* Mouse events pass through to the standard input. */
position: absolute;
right: 14px;
top: 15px;
} }
.custom-slider > .bar > .filled, .time-container > .time {
.custom-slider > .bar > .cap {
bottom: 0;
position: absolute; position: absolute;
top: 0; right: 8px; /* Should be same as time-container's right padding. */
} top: 8px; /* Should be same as time-container's top padding. */
/* The filled portion of the slider bar to the left of the thumb. */
.custom-slider > .bar > .filled {
background-image: -webkit-image-set(
url(../assets/100/player_timeline_played_center.png) 1x,
url(../assets/200/player_timeline_played_center.png) 2x);
border-left-style: none;
border-right-style: none;
left: 0;
width: 0; /* The element style.width is manipulated from the code. */
} }
/* Rounded caps to the left and right of the slider bar. */ .time-container > .time.disabled {
.custom-slider > .bar > .cap { opacity: 0;
width: 1px;
} }
/* Left cap is always filled, should be the same color as .filled. */ .time-container > .time-spacer {
.custom-slider > .bar > .cap.left { opacity: 0; /* This class is intended to be used as invisible spacer. */
background-image: -webkit-image-set(
url(../assets/100/player_timeline_played_left.png) 1x,
url(../assets/200/player_timeline_played_left.png) 2x);
right: 100%;
} }
/* Right cap is always not filled. */ .time-controls > paper-slider {
.custom-slider > .bar > .cap.right { --paper-slider-active-color: rgb(66, 133, 244);
background-image: -webkit-image-set( --paper-slider-knob-color: rgb(64, 138, 241);
url(../assets/100/player_timeline_base_right.png) 1x, flex: auto;
url(../assets/200/player_timeline_base_right.png) 2x);
left: 100%;
} }
.media-button.disabled, .media-button.disabled,
.custom-slider.disabled, paper-slider.disabled {
.custom-slider.readonly {
pointer-events: none; pointer-events: none;
} }
/* Progress seek marker (precise time shown on mouse hover. */
/* Thin vertical line across the slider bar */
.custom-slider > .bar > .seek-mark {
background-color: #202020;
bottom: -1px;
left: 0;
position: absolute;
top: -1px;
width: 0;
}
.custom-slider > .bar > .seek-mark.visible {
width: 1px;
}
.custom-slider > .bar > .seek-mark.inverted {
background-color: #808080;
}
/* Text label giving the precise time corresponding to the hover position. */
.custom-slider > .bar > .seek-mark > .seek-label {
align-items: center;
background: #202020;
border-top-left-radius: 2px;
border-top-right-radius: 2px;
bottom: 19px;
color: white;
display: flex;
flex-direction: row;
font-size: 12px;
height: 15px;
justify-content: center;
left: 0;
opacity: 0;
overflow: hidden;
position: absolute;
transition: opacity 150ms ease;
}
.custom-slider > .bar > .seek-mark.visible > .seek-label {
opacity: 1;
}
/* Media controls in order of appearance. */ /* Media controls in order of appearance. */
.audio-controls { .audio-controls {
...@@ -363,27 +228,16 @@ ...@@ -363,27 +228,16 @@
} }
/* Invisible div used to compute the width required for the elapsed time. */ /* Invisible div used to compute the width required for the elapsed time. */
.time-controls > .time > .duration,
.time-controls > .time > .current { .time-controls > .time > .current {
align-items: center; align-items: center;
color: rgb(51, 51, 51);
display: flex; display: flex;
flex-direction: row; flex-direction: row;
font-size: 12px;
height: 100%; height: 100%;
justify-content: flex-end; justify-content: flex-end;
position: absolute; position: absolute;
top: -1px; top: -1px;
} }
.time-controls > .time > .duration {
left: 0;
}
.time-controls > .time > .current {
right: 0;
}
/* Volume controls: sound button and volume slider */ /* Volume controls: sound button and volume slider */
#volumeContainer { #volumeContainer {
......
...@@ -6,6 +6,7 @@ ...@@ -6,6 +6,7 @@
<link rel="import" href="chrome://resources/polymer/v1_0/polymer/polymer.html"> <link rel="import" href="chrome://resources/polymer/v1_0/polymer/polymer.html">
<link rel="import" href="chrome://resources/polymer/v1_0/font-roboto/roboto.html"> <link rel="import" href="chrome://resources/polymer/v1_0/font-roboto/roboto.html">
<link rel="import" href="chrome://resources/polymer/v1_0/paper-slider/paper-slider.html">
<link rel="import" href="volume_controller.html"> <link rel="import" href="volume_controller.html">
<link rel="import" href="chrome-extension://hhaomjibdihmijegdhdafkllkbggdgoj/foreground/elements/files_ripple.html"> <link rel="import" href="chrome-extension://hhaomjibdihmijegdhdafkllkbggdgoj/foreground/elements/files_ripple.html">
<link rel="import" href="chrome-extension://hhaomjibdihmijegdhdafkllkbggdgoj/foreground/elements/files_toggle_ripple.html"> <link rel="import" href="chrome-extension://hhaomjibdihmijegdhdafkllkbggdgoj/foreground/elements/files_toggle_ripple.html">
...@@ -14,24 +15,7 @@ ...@@ -14,24 +15,7 @@
<link rel="import" type="css" href="control_panel.css"> <link rel="import" type="css" href="control_panel.css">
<template> <template>
<div class="controls"> <div class="controls">
<div class="upper-controls time-controls"> <div class="upper-controls audio-controls">
<div class="time media-control">
<div class="current">[[time2string_(time)]]</div>
</div>
<div class="progress media-control custom-slider">
<input id="timeInput" name="timeInput" type="range" touch-action="manipulation"
min="0" max="[[duration]]" value="{{time::input}}">
<div class="bar">
<div class="filled" style$="[[computeProgressBarStyle_(time, duration)]]"></div>
<div class="cap left"></div>
<div class="cap right"></div>
</div>
</div>
<div class="time media-control">
<div class="duration">[[time2string_(duration)]]</div>
</div>
</div>
<div class="lower-controls audio-controls">
<!-- Shuffle toggle button in the bottom line. --> <!-- Shuffle toggle button in the bottom line. -->
<button class="shuffle-mode media-button toggle" state="default"> <button class="shuffle-mode media-button toggle" state="default">
<label> <label>
...@@ -64,7 +48,7 @@ ...@@ -64,7 +48,7 @@
</button> </button>
<!-- Play button in the bottom line. --> <!-- Play button in the bottom line. -->
<button class="play media-control media-button" <button class="play media-button"
state$="[[computePlayState_(playing)]]" state$="[[computePlayState_(playing)]]"
on-click="playClick"> on-click="playClick">
<div class="normal playing"></div> <div class="normal playing"></div>
...@@ -115,6 +99,13 @@ ...@@ -115,6 +99,13 @@
<files-toggle-ripple activated="[[expanded]]"></files-toggle-ripple> <files-toggle-ripple activated="[[expanded]]"></files-toggle-ripple>
</button> </button>
</div> </div>
<div class="lower-controls time-controls">
<div class="time-container">
<div class="time-spacer">[[computeTimeString_(duration, duration)]]</div>
<div class="time">[[computeTimeString_(time, duration)]]</div>
</div>
<paper-slider id="timeSlider" max="[[duration]]" value="{{time::change}}"></paper-slider>
</div>
</div> </div>
</template> </template>
</dom-module> </dom-module>
......
...@@ -99,6 +99,15 @@ ...@@ -99,6 +99,15 @@
value: false, value: false,
observer: 'volumeSliderShownChanged', observer: 'volumeSliderShownChanged',
notify: true notify: true
},
/**
* Whether the knob of time slider is being dragged.
*/
dragging: {
type: Boolean,
value: false,
notify: true
} }
}, },
...@@ -112,17 +121,14 @@ ...@@ -112,17 +121,14 @@
this.$.volumeSlider.addEventListener('focusout', onFocusoutBound); this.$.volumeSlider.addEventListener('focusout', onFocusoutBound);
this.$.volumeButton.addEventListener('focusout', onFocusoutBound); this.$.volumeButton.addEventListener('focusout', onFocusoutBound);
// Prevent the time slider from being moved by arrow keys. this.$.timeSlider.addEventListener('value-change', function() {
this.$.timeInput.addEventListener('keydown', function(event) { if (this.dragging)
switch (event.keyCode) { this.dragging = false;
case 37: // Left arrow }.bind(this));
case 38: // Up arrow this.$.timeSlider.addEventListener('immediate-value-change', function() {
case 39: // Right arrow if (!this.dragging)
case 40: // Down arrow this.dragging = true;
event.preventDefault(); }.bind(this));
break;
};
});
}, },
/** /**
...@@ -193,6 +199,16 @@ ...@@ -193,6 +199,16 @@
return ~~(time / 60000) + ':' + ('0' + ~~(time / 1000 % 60)).slice(-2); return ~~(time / 60000) + ':' + ('0' + ~~(time / 1000 % 60)).slice(-2);
}, },
/**
* Converts the time and duration into human friendly string.
* @param {number} time Time to be converted.
* @param {number} duration Duration to be converted.
* @return {string} String representation of the given time
*/
computeTimeString_: function(time, duration) {
return this.time2string_(time) + ' / ' + this.time2string_(duration);
},
/** /**
* Computes state for play button based on 'playing' property. * Computes state for play button based on 'playing' property.
* @return {string} * @return {string}
......
...@@ -43,6 +43,7 @@ ...@@ -43,6 +43,7 @@
.track .icon { .track .icon {
background-position: center; background-position: center;
background-repeat: no-repeat; background-repeat: no-repeat;
flex: none;
height: 32px; height: 32px;
margin: 8px; margin: 8px;
width: 32px; width: 32px;
......
...@@ -332,14 +332,14 @@ AudioPlayer.HEADER_HEIGHT = 33; // 32px + border 1px ...@@ -332,14 +332,14 @@ AudioPlayer.HEADER_HEIGHT = 33; // 32px + border 1px
* @type {number} * @type {number}
* @const * @const
*/ */
AudioPlayer.TRACK_HEIGHT = 44; AudioPlayer.TRACK_HEIGHT = 48;
/** /**
* Controls bar height in pixels. * Controls bar height in pixels.
* @type {number} * @type {number}
* @const * @const
*/ */
AudioPlayer.CONTROLS_HEIGHT = 73; // 72px + border 1px AudioPlayer.CONTROLS_HEIGHT = 96;
/** /**
* Default number of items in the expanded mode. * Default number of items in the expanded mode.
......
...@@ -21,10 +21,10 @@ var AUDIO_PLAYER_APP_URL = 'audio_player.html'; ...@@ -21,10 +21,10 @@ var AUDIO_PLAYER_APP_URL = 'audio_player.html';
var audioPlayerCreateOptions = { var audioPlayerCreateOptions = {
id: 'audio-player', id: 'audio-player',
type: 'panel', type: 'panel',
minHeight: 48 + 73, // 48px: track, 73px: controller minHeight: 48 + 96, // 48px: track, 96px: controller
minWidth: 292, minWidth: 280,
height: 48 + 73, // collapsed height: 48 + 96, // collapsed
width: 292 width: 280
}; };
/** /**
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment