Skip to content

Instantly share code, notes, and snippets.

Created September 9, 2017 10:59
Show Gist options
  • Save anonymous/e61d375b7bd84ad62c73d5246f60f4e7 to your computer and use it in GitHub Desktop.
Save anonymous/e61d375b7bd84ad62c73d5246f60f4e7 to your computer and use it in GitHub Desktop.
Svelte component
<div class="player">
<audio ref:audio :src preload="none"
bind:currentTime bind:duration bind:paused
bind:buffered bind:played bind:seekable
/>
<div class="progress-bar">
<ProgressBar :currentTime :duration :buffered
:played :seekable on:seek="seek(event.position)"
/>
</div>
<img on:click="playpause()" src="{{icon}}/EEEEEE">
<span class="time elapsed">{{format(currentTime)}}</span>
<span class="time duration">{{format(duration)}}</span>
</div>
<style>
.player {
position: relative;
margin: 50px auto;
max-width: 600px;
min-width: 200px;
height: 100px;
background: #333;
}
.progress-bar {
padding: 20px;
height: 20px;
}
img, .time {
position: absolute;
bottom: 10px;
height: 40px;
}
img {
left: calc(50% - 20px);
cursor: pointer;
}
.time {
display: inline-block;
width: calc(50% - 30px);
line-height: 40px;
color: #ccc;
}
.elapsed {
left: 0;
text-align: right;
}
.duration {
right: 0;
}
</style>
<script>
import ProgressBar from './ProgressBar.html';
export default {
components: {
ProgressBar,
},
data() {
return {
paused: true,
};
},
computed: {
icon(paused) {
return `https://icon.now.sh/${paused ? 'play' : 'pause'}_circle_filled`;
},
},
methods: {
playpause() {
if (this.get('paused')) {
this.refs.audio.play();
} else {
this.refs.audio.pause();
}
},
seek(position) {
this.set({ currentTime: position });
},
},
helpers: {
format(time) {
if (isNaN(time)) return '--:--.-';
const minutes = Math.floor(time / 60);
const seconds = (time % 60).toFixed(1);
return minutes + ':' + pad(seconds);
}
},
};
function pad(num) {
return num < 10 ? '0' + num : num;
}
</script>
{
"src": "https://api.soundcloud.com/tracks/314855600/stream?client_id=2t9loNQH90kzJcsFCODdigxfp325aq4z"
}
<div :style on:dragover="event.preventDefault()">
<TimeRanges :duration list={{buffered}} color="{{bufferedColor}}" />
<TimeRanges :duration list={{played}} color="{{playedColor}}" />
<TimeHandle :duration :currentTime color="{{handleColor}}" />
<TimeRanges :duration list={{seekable}} color="{{seekableColor}}" on:seek="onseek(event)" cursor="pointer" />
</div>
<style>
div {
position: relative;
width: 100%;
height: 100%;
}
</style>
<script>
import TimeRanges from './TimeRanges.html';
import TimeHandle from './TimeHandle.html';
export default {
components: {
TimeRanges,
TimeHandle,
},
data() {
return {
backgroundColor: 'rgba(255, 255, 255, 0.1)',
bufferedColor: 'rgba(255, 255, 255, 0.15)',
playedColor: '#DD0000',
seekableColor: 'rgba(0, 0, 0, 0)', // transparent
handleColor: '#EEEEEE',
};
},
computed: {
style(backgroundColor) {
return `background: ${backgroundColor};`;
},
},
methods: {
onseek(data) {
this.fire('seek', data);
},
}
};
</script>
<div ref:node :style><span /></div>
<style>
div {
position: absolute;
top: 0;
width: 14px;
height: 100%;
transform: translateX(-7px);
}
span {
display: block;
position: absolute;
top: 0;
left: 50%;
width: 2px;
height: calc(100% + 2px);
background: white;
border-radius: 1px;
transform: translateX(-1px) translateY(-1px);
}
</style>
<script>
export default {
computed: {
style(currentTime, duration, color) {
if (!duration || isNaN(duration) || !currentTime || isNaN(currentTime)) {
return 'display: none';
}
return `left: ${currentTime * 100 / duration}%`;
},
},
};
</script>
<div ref:node :style on:click="onclick(event)" />
<style>
div {
position: absolute;
top: 0;
height: 100%;
}
</style>
<script>
export default {
data() {
return {
start: 0,
end: 0,
duration: NaN,
color: 'black',
cursor: 'auto',
};
},
computed: {
style(start, end, duration, color, cursor) {
if (!duration || isNaN(duration)) {
return 'left: 0; width: 0';
}
const left = start * 100 / duration;
const width = end * 100 / duration - left;
return `left: ${left}%; width: ${width}%; background: ${color}; cursor: ${cursor}`;
},
},
methods: {
onclick(e) {
const rect = this.refs.node.parentElement.getBoundingClientRect();
const width = Math.floor(rect.right - rect.left);
const offset = event.clientX - rect.left;
const position = offset / width * this.get('duration');
this.fire('seek', { position });
},
},
};
</script>
<div ref:node />
<style>
div {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
}
</style>
<script>
import TimeRange from './TimeRange.html';
export default {
components: {
TimeRange,
},
data() {
return {
duration: NaN,
list: [],
cursor: 'auto',
};
},
oncreate() {
this.ranges = [];
this.colorObserver = this.observe('color', this.updateColor.bind(this));
this.durationObserver = this.observe('duration', this.updateDuration.bind(this));
this.cursorObserver = this.observe('cursor', this.updateCursor.bind(this));
this.listObserver = this.observe('list', this.updateList.bind(this));
},
ondestroy() {
this.listObserver.cancel();
this.cursorObserver.cancel();
this.durationObserver.cancel();
this.colorObserver.cancel();
this.clear();
},
methods: {
updateColor(color) {
this.ranges.forEach((range) => range.set({ color }));
},
updateDuration(duration) {
this.ranges.forEach((range) => range.set({ duration }));
},
updateCursor(cursor) {
this.ranges.forEach((range) => range.set({ cursor }));
},
updateList(items) {
const diff = items.length - this.ranges.length;
// re-use existing ranges for performance
if (diff >= 0) {
for (let i = 0, range; i < items.length; i++) {
if (i < this.ranges.length) {
this.ranges[i].set({
start: items.start(i),
end: items.end(i),
});
} else {
range = new TimeRange({
target: this.refs.node,
data: {
duration: this.get('duration'),
start: items.start(i),
end: items.end(i),
color: this.get('color'),
cursor: this.get('cursor'),
},
});
range.on('seek', (data) => this.fire('seek', data));
this.ranges.push(range);
}
}
} else {
for (let i = this.ranges.length - 1, range; i >= 0; i--) {
if (i >= items.length) {
range = this.ranges.pop();
range.destroy();
range = null;
} else {
this.ranges[i].set({
start: items.start(i),
end: items.end(i),
});
}
}
}
},
clear() {
this.ranges.forEach((range) => {
range.destroy();
range = null;
});
this.ranges = [];
},
}
};
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment