Skip to content
This repository has been archived by the owner on Feb 22, 2023. It is now read-only.

Create the audio track component #127

Merged
merged 42 commits into from
Aug 19, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
4e451d0
Refactor filter and search store
obulat Aug 10, 2021
466b0f4
Install and configure Storybook
dhruvkb Aug 13, 2021
9a98597
Add an introductory story
dhruvkb Aug 13, 2021
e7a6703
Add more colors and derive `fill` from them
dhruvkb Aug 14, 2021
8576081
Define utils for resampling data points
dhruvkb Aug 14, 2021
3a56b71
Define the waveform component
dhruvkb Aug 14, 2021
09f36c7
Fix bug in waveform progress bar height
dhruvkb Aug 14, 2021
619a457
Emit 'sought' event when clicked
dhruvkb Aug 14, 2021
e0989d0
set progress on user click
zackkrida Aug 14, 2021
49ddee9
preview width
zackkrida Aug 14, 2021
b1ed790
show hover preview progress
zackkrida Aug 14, 2021
a711c9e
remove log
zackkrida Aug 14, 2021
e2cc89f
Remove added border radius
zackkrida Aug 14, 2021
76a0ff1
Add the play/pause toggle button
dhruvkb Aug 14, 2021
86acd17
Create the `AudioTrack` component
dhruvkb Aug 14, 2021
65301b9
Document the props and the component itself
dhruvkb Aug 14, 2021
3fcec13
Add state for seeking behind progressbar
dhruvkb Aug 14, 2021
db6b254
Propagate peaks to the `Waveform` component
dhruvkb Aug 16, 2021
a3cd80e
Include the waveform for 'Eine kleine Nachtmusik'
dhruvkb Aug 16, 2021
4de6a15
Correct the hierarchy of components
dhruvkb Aug 16, 2021
1e441cc
Remove redundant rectangle
dhruvkb Aug 16, 2021
e3f4d60
Replace `percentage` prop with raw time values
dhruvkb Aug 16, 2021
3d40017
Add scale up animation
dhruvkb Aug 16, 2021
b7c6e4b
Add a partially translucent disabled state for the button
dhruvkb Aug 17, 2021
7b6a1b1
Add floating timestamps on the waveform
dhruvkb Aug 17, 2021
8b758e7
Add a loading message when the waveform is not ready
dhruvkb Aug 17, 2021
9bb55e8
Replace 'preview' with more appropriate 'seek'
dhruvkb Aug 17, 2021
bcdff3d
Update event name to match that of `HTMLAudioElement`
dhruvkb Aug 17, 2021
c4fef13
Internationalise audio title
dhruvkb Aug 17, 2021
de0154e
Replace progress with current time and duration
dhruvkb Aug 17, 2021
e1e00a1
Clear up flow of state between components and audio element
dhruvkb Aug 17, 2021
cf5422e
Handle async state change of player
dhruvkb Aug 17, 2021
c8c56b9
Fix after rebasing audio
zackkrida Aug 17, 2021
f01cf2f
Reduce magic numbers in height computation
dhruvkb Aug 17, 2021
89b86b0
Increase bar height in the waveform
dhruvkb Aug 18, 2021
abf887c
Add some more documentation about the `PlayPause` component
dhruvkb Aug 19, 2021
d5431a1
Increase the size of the play/pause button to match the mockup more c…
dhruvkb Aug 19, 2021
5672969
Remove redundant background
dhruvkb Aug 19, 2021
730e19b
Replace margin with gap
dhruvkb Aug 19, 2021
911e09a
Make the padding uniform on both sides
dhruvkb Aug 19, 2021
9e54826
Add option to show duration at the right edge
dhruvkb Aug 19, 2021
eb40cbe
Enlarge the play/pause icon to match 6513680
dhruvkb Aug 19, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,6 @@ selenium-debug.log
vercel.json
.eslintcache
.nuxt

.vercel
.nuxt-storybook
storybook-static
.vercel
21 changes: 21 additions & 0 deletions nuxt.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -183,4 +183,25 @@ export default {
// https://github.com/nuxt-community/tailwindcss-module/issues/114#issuecomment-698885369
configPath: '~~/tailwind.config.js',
},
storybook: {
port: 6006, // standard port for Storybook
stories: ['~/**/*.stories.@(mdx|js)'],
addons: [
{
name: '@storybook/addon-essentials',
options: {
backgrounds: false,
viewport: false,
toolbars: false,
},
},
],
parameters: {
options: {
storySort: {
order: ['Introduction', ['Openverse UI']],
},
},
},
},
}
19,243 changes: 15,105 additions & 4,138 deletions package-lock.json

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"build": "nuxt build",
"generate": "nuxt generate",
"start": "nuxt start",
"storybook": "nuxt storybook",
"test": "jest",
"test:unit": "jest",
"test:e2e": "cypress open",
Expand Down Expand Up @@ -59,14 +60,17 @@
"vue-i18n": "^8.24.3"
},
"devDependencies": {
"@babel/runtime-corejs3": "^7.15.3",
"@intlify/eslint-plugin-vue-i18n": "^0.11.1",
"@nuxt/types": "^2.15.4",
"@nuxtjs/eslint-module": "^3.0.2",
"@nuxtjs/storybook": "^4.1.1",
"@nuxtjs/style-resources": "^1.0.0",
"@nuxtjs/tailwindcss": "^4.2.1",
"@types/jest": "^26.0.22",
"@vue/test-utils": "^1.1.3",
"autoprefixer": "^10.3.1",
"core-js": "^3.16.1",
"cypress": "^6.0.0",
"eslint": "^7.24.0",
"eslint-config-prettier": "^8.1.0",
Expand Down
5 changes: 5 additions & 0 deletions src/assets/icons/pause.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 5 additions & 0 deletions src/assets/icons/play.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
141 changes: 141 additions & 0 deletions src/components/AudioTrack/AudioTrack.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
<template>
<div class="audio-track">
<div class="waveform-section">
<Waveform
class="h-30 w-full"
:is-ready="isReady"
:current-time="currentTime"
:duration="duration"
:peaks="audio.peaks"
@seeked="setPosition"
/>
</div>
<div class="info-section flex flex-row gap-6">
<PlayPause
class="self-start flex-shrink-0"
:is-playing="isPlaying"
:disabled="!isReady"
@toggle="setPlayerState"
/>
<div class="info self-end">
<i18n path="audio-track.title" tag="p">
<template #title>
<strong>{{ audio.title }}</strong>
</template>
<template #creator>
<a
class="text-pink hover:text-pink hover:underline"
:href="audio.creatorUrl"
>{{ audio.creator }}</a
>
</template>
</i18n>
<p class="-mt-2">
{{ durationFmt }}
</p>
</div>
</div>

<!-- eslint-disable vuejs-accessibility/media-has-caption -->
<audio
v-show="false"
ref="audio"
controls
:src="audio.url"
crossorigin="anonymous"
@loadedmetadata="
setIsReady()
updateTime()
"
@play="setIsPlaying(true)"
@pause="setIsPlaying(false)"
/>
<!-- eslint-enable vuejs-accessibility/media-has-caption -->
</div>
</template>

<script>
import Waveform from '~/components/AudioTrack/Waveform.vue'
import PlayPause from '~/components/AudioTrack/PlayPause.vue'

/**
* Displays the waveform and basic information about the track, along with
* controls to play, pause or seek to a point on the track.
*/
export default {
name: 'AudioTrack',
components: { PlayPause, Waveform },
props: {
/**
* the information about the track, typically from a track's detail endpoint
*/
audio: {
type: Object,
required: true,
},
},
data: () => ({
player: null, // HTMLAudioElement
currentTime: 0,
duration: 0,

isReady: false,
isPlaying: false,
}),
computed: {
/**
* Get the duration of the song in hh:mm:ss format, dropping the hour part
* if it is zero.
* @returns {string} the duration in a human-friendly format
*/
durationFmt() {
const seconds = (this.audio.duration ?? 0) / 1e3 // ms -> s
const date = new Date(0)
date.setSeconds(seconds)
return date.toISOString().substr(11, 8).replace(/^00:/, '')
},
},
mounted() {
this.player = this.$refs.audio
},
methods: {
updateTime() {
this.currentTime = this.player.currentTime
this.duration = this.player.duration
},
syncTime() {
if (this.player) {
this.updateTime()
}
if (this.isPlaying) {
// still playing, keep looping
window.requestAnimationFrame(this.syncTime)
}
},

// Subcomponent events
setPosition(percentage) {
if (this.player.duration) {
this.player.currentTime = this.player.duration * percentage
this.updateTime()
}
},
async setPlayerState(isPlaying) {
if (isPlaying) {
await this.player.play()
window.requestAnimationFrame(this.syncTime)
} else {
await this.player.pause()
}
},

// HTMLAudioElement events
setIsReady() {
this.isReady = true
},
setIsPlaying(isPlaying) {
this.isPlaying = isPlaying
},
},
}
</script>
47 changes: 47 additions & 0 deletions src/components/AudioTrack/PlayPause.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<template>
<button
class="bg-dark-charcoal h-20 w-20 flex items-center justify-center disabled:opacity-70"
@click="toggle"
>
<svg
class="text-white h-8 w-8"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
>
<use v-if="isPlaying" :href="`${icons.pause}#icon`" />
<use v-else :href="`${icons.play}#icon`" />
</svg>
</button>
</template>

<script>
import playIcon from '~/assets/icons/play.svg'
import pauseIcon from '~/assets/icons/pause.svg'

/**
* Displays the control for switching between the playing and paused states of
* a media file.
*/
export default {
name: 'PlayPause',
props: {
/**
* whether the media associated with the button is currently playing
*/
isPlaying: {
type: Boolean,
},
},
data: () => ({
icons: {
play: playIcon,
pause: pauseIcon,
},
}),
methods: {
toggle() {
this.$emit('toggle', !this.isPlaying)
},
},
}
</script>
Loading