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

Active Media Vuex Model + Composable AudioTrack #207

Merged
merged 35 commits into from
Sep 16, 2021
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
a561d65
Create Vuex module to hold information about active media
dhruvkb Sep 12, 2021
90fc1e0
Merge `ActiveMediaStore` into the main Vuex store
dhruvkb Sep 12, 2021
a4d79e7
Reduce font size to comply with the new typography style guide
dhruvkb Sep 12, 2021
3e02466
Make the play/pause toggle button `v-model` compatible
dhruvkb Sep 12, 2021
15cab4c
Create `AudioController` to interface between `Waveform`, `HTMLAudioE…
dhruvkb Sep 12, 2021
bbeb502
Fix bug in dynamic ARIA label
dhruvkb Sep 12, 2021
b60b4a5
Unset default size for play-pause button
dhruvkb Sep 13, 2021
0dbec69
Extend AudioTrack to be composable using layouts
dhruvkb Sep 13, 2021
ffddfd4
Document single-play behavior with a story
dhruvkb Sep 13, 2021
c5a310e
Change base font-size to 14px
dhruvkb Sep 13, 2021
a3498ff
Change audio title to a `h1` tag
dhruvkb Sep 13, 2021
1f6fe83
Change font-sizes to a 16px scale with unchanged pixel values
dhruvkb Sep 13, 2021
ed90854
Make the layouts responsible for stying the audio UI
dhruvkb Sep 13, 2021
e63dd7d
Populate creator and license info on the audio samples
dhruvkb Sep 13, 2021
00ab1f5
Create a row layout for the audio player
dhruvkb Sep 13, 2021
795acc0
Create the `License` component for rending the license name and icon
dhruvkb Sep 13, 2021
f2179e3
Beautify duration and license rendering
dhruvkb Sep 13, 2021
b7baf27
Use row layout for multiple players
dhruvkb Sep 13, 2021
012f30a
Increase separation for the CC icon
dhruvkb Sep 13, 2021
f11dfae
Update the full layout to show artist and duration
dhruvkb Sep 13, 2021
f5e63b2
Translate audio categories
dhruvkb Sep 13, 2021
a67800c
Extract license constants into a separate file for reuse
dhruvkb Sep 14, 2021
c80de9e
Change `div` to `article` and link to the single audio page
dhruvkb Sep 14, 2021
6865046
Merge branch 'main' of https://github.com/WordPress/openverse-fronten…
dhruvkb Sep 14, 2021
e88a064
Use SVGs instead of icon font for licenses
dhruvkb Sep 14, 2021
752d149
Move text-color to a higher level to allow overriding
dhruvkb Sep 16, 2021
1f62660
Use the `.row-track` class to narrow down unscoped styles
dhruvkb Sep 16, 2021
07b31b9
Use slot props to allow layouts to modify components
dhruvkb Sep 16, 2021
0fb988a
Remove redundant class
dhruvkb Sep 16, 2021
bdb2f1c
Expand 'Thumb' to 'Thumbnail' for clarity
dhruvkb Sep 16, 2021
526b81a
Fix a typo in the documentation
dhruvkb Sep 16, 2021
da4ff50
Move the wrapper from thumbnail to layout
dhruvkb Sep 16, 2021
323d062
Use the correct type annotations for destructured args
dhruvkb Sep 16, 2021
a1bef1f
Merge branch 'main' of https://github.com/WordPress/openverse-fronten…
dhruvkb Sep 16, 2021
c08436f
Merge remote-tracking branch 'origin/active_media' into active_media
dhruvkb Sep 16, 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
190 changes: 190 additions & 0 deletions src/components/AudioTrack/AudioController.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
<template>
<div class="audio-controller">
<Waveform
:class="waveformClasses"
:message="message ? $t(`audio-track.messages.${message}`) : null"
:current-time="currentTime"
:duration="duration"
:peaks="audio.peaks"
@seeked="handleSeeked"
/>

<!-- eslint-disable vuejs-accessibility/media-has-caption -->
<audio
v-show="false"
v-bind="$attrs"
:id="audio.id"
ref="audioEl"
class="audio-controller"
controls
:src="audio.url"
crossorigin="anonymous"
@loadedmetadata="handleReady"
@error="handleError"
/>
<!-- eslint-enable vuejs-accessibility/media-has-caption -->
</div>
</template>

<script>
import Waveform from '~/components/AudioTrack/Waveform'
import { computed, ref, useStore, watch } from '@nuxtjs/composition-api'
import {
SET_ACTIVE_MEDIA_ITEM,
UNSET_ACTIVE_MEDIA_ITEM,
} from '~/store-modules/mutation-types'

/**
* Controls the interaction between the parent Vue component, the underlying
* HTMLAudioElement and the Active Media Store. Also displays the waveform that
* is deeply linked to timekeeping for the HTMLAudioElement.
*/
export default {
name: 'AudioController',
components: { Waveform },
inheritAttrs: false,
model: {
prop: 'status',
event: 'change',
},
props: {
/**
* the information about the track, typically from a track's detail endpoint
*/
audio: {
type: Object,
required: true,
},
/**
* the playing/paused status of the audio
*/
status: {
type: String,
required: true,
validator: (val) => ['playing', 'paused'].includes(val),
},
/**
* the CSS classes to apply on the waveform; This can take any form
* acceptable to Vue class bindings.
*/
waveformClasses: {},
},
setup(props, { emit }) {
const store = useStore()

const audioEl = ref(null) // template ref

/* Status */

const isActiveTrack = computed(
() =>
store.state.activeMediaType === 'audio' &&
store.state.activeMediaId === props.audio.id
)
// Sync status from parent to player and store
watch(
() => props.status,
(status) => {
if (!audioEl.value) return

switch (status) {
case 'playing':
audioEl.value.play()
store.commit(SET_ACTIVE_MEDIA_ITEM, {
type: 'audio',
id: props.audio.id,
})
window.requestAnimationFrame(updateTimeLoop)
break
case 'paused':
audioEl.value.pause()
if (isActiveTrack.value) {
store.commit(UNSET_ACTIVE_MEDIA_ITEM)
}
break
}
}
)
// Sync status from store to parent
watch(
() => [store.state.activeMediaType, store.state.activeMediaId],
() => {
const status = isActiveTrack.value ? 'playing' : 'paused'
emit('change', status)
}
)

/* Error handling */

const message = ref('loading')
const handleError = (event) => {
const error = event.target.error
let errorMsg
switch (error.code) {
case error.MEDIA_ERR_ABORTED:
errorMsg = 'err_aborted'
break
case error.MEDIA_ERR_NETWORK:
errorMsg = 'err_network'
break
case error.MEDIA_ERR_DECODE:
errorMsg = 'err_decode'
break
case error.MEDIA_ERR_SRC_NOT_SUPPORTED:
errorMsg = 'err_unsupported'
break
}
message.value = errorMsg
}

/* Timekeeping */

const currentTime = ref(0)
const duration = ref(0)
const updateTime = () => {
if (!audioEl.value) return

currentTime.value = audioEl.value.currentTime
duration.value = audioEl.value.duration
}
const updateTimeLoop = () => {
updateTime()
if (props.status === 'playing') {
// Audio is playing, keep looping
window.requestAnimationFrame(updateTimeLoop)
}
}

/* Metadata readiness */

const handleReady = () => {
message.value = null
updateTime()
emit('ready')
}

/* Seeking */

const handleSeeked = (frac) => {
if (audioEl.value && duration.value) {
audioEl.value.currentTime = duration.value * frac
updateTime()
}
}

return {
audioEl,

message,
handleError,

currentTime,
duration,

handleReady,

handleSeeked,
}
},
}
</script>
18 changes: 18 additions & 0 deletions src/components/AudioTrack/AudioThumb.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<template>
<!-- Should have a fixed width -->
dhruvkb marked this conversation as resolved.
Show resolved Hide resolved
<div>
<div class="h-0 w-full pt-full bg-yellow">&nbsp;</div>
</div>
dhruvkb marked this conversation as resolved.
Show resolved Hide resolved
</template>

<script>
export default {
name: 'AudioThumb',
dhruvkb marked this conversation as resolved.
Show resolved Hide resolved
props: {
audio: {
type: Object,
required: true,
},
},
}
</script>
Loading