Skip to content

Commit

Permalink
feat: include speed and playbackRate in share url (#132)
Browse files Browse the repository at this point in the history
  • Loading branch information
cdrani authored Sep 27, 2023
1 parent dd16fc7 commit b2645d0
Show file tree
Hide file tree
Showing 17 changed files with 455 additions and 449 deletions.
77 changes: 31 additions & 46 deletions src/actions/init.js
Original file line number Diff line number Diff line change
@@ -1,92 +1,77 @@
import { spotifyVideo } from './overload.js'

import Alert from '../models/alert.js'
import { store } from '../stores/data.js'
import TrackList from '../models/tracklist/track-list.js'
import { spotifyVideo } from './overload.js'
import CurrentSnip from '../models/snip/current-snip.js'
import TrackList from '../models/tracklist/track-list.js'
import NowPlayingIcons from '../models/now-playing-icons.js'

import TrackListObserver from '../observers/track-list.js'
import NowPlayingObserver from '../observers/now-playing.js'
import CurrentTimeObserver from '../observers/current-time.js'
import CurrentTimeObserver from '../observers/current-time/current-time.js'

class App {
#video
#store
#snip
#alert
#intervalId
#active = true
#nowPlayingIcons
#currentTimeObserver
#nowPlayingObserver
#trackListObserver

constructor({ video, store }) {
this.#store = store
this.#video = video
this._store = store
this._video = video
this._active = true
this._intervalId = null

this.#init()
}

#init() {
this.#snip = new CurrentSnip()

this.#alert = new Alert()
this._snip = new CurrentSnip()

this.#nowPlayingIcons = new NowPlayingIcons(this.#snip)
this.#nowPlayingObserver = new NowPlayingObserver({ snip: this.#snip, video: this.#video })
this.#trackListObserver = new TrackListObserver(new TrackList(this.#store))
this.#currentTimeObserver = new CurrentTimeObserver({ video: this.#video, snip: this.#snip })

this.#snip.updateView()
this._nowPlayingIcons = new NowPlayingIcons(this._snip)
this._currentTimeObserver = new CurrentTimeObserver(this._video)
this._trackListObserver = new TrackListObserver(new TrackList(this._store))
this._nowPlayingObserver = new NowPlayingObserver({ snip: this._snip, video: this._video })

this._snip.updateView()
this.#resetInterval()

this.#reInit()
}

#resetInterval() {
if (!this.#intervalId) return
if (!this._intervalId) return

clearInterval(this.#intervalId)
this.#intervalId = null
clearInterval(this._intervalId)
this._intervalId = null
}

disconnect() {
this.#active = false
this.#video.reset()
this._active = false
this._video.reset()

this.#trackListObserver.disconnect()
this.#currentTimeObserver.disconnect()
this.#nowPlayingObserver.disconnect()
this._trackListObserver.disconnect()
this._currentTimeObserver.disconnect()
this._nowPlayingObserver.disconnect()

this.#resetInterval()
}

async connect() {
this.#active = true
this._active = true

this.#trackListObserver.observe()
this.#currentTimeObserver.observe()
this.#nowPlayingObserver.observe()
this._trackListObserver.observe()
this._currentTimeObserver.observe()
this._nowPlayingObserver.observe()

this.#resetInterval()
await this.#video.activate()
await this._video.activate()

this.#reInit()
}

#reInit() {
this.#intervalId = setInterval(async () => {
if (!this.#active) return
if (!this.#intervalId) return
this._intervalId = setInterval(async () => {
if (!this._active) return
if (!this._intervalId) return

const chorus = document.getElementById('chorus')

if (!chorus) {
this.#nowPlayingIcons.init()
await this.#video.activate()
this._nowPlayingIcons.init()
await this._video.activate()
}
}, 3000)
}
Expand Down
2 changes: 1 addition & 1 deletion src/actions/overload.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import VideoElement from '../models/video.js'
import VideoElement from '../models/video/video.js'

class SpotifyVideo {
#video
Expand Down
36 changes: 36 additions & 0 deletions src/data/song-state.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { currentData } from './current.js'
import { currentSongInfo } from '../utils/song.js'

const sharedSnipValues = () => {
if (!location?.search) return

const params = new URLSearchParams(location.search)
const values = params?.get('ch')
if (!values) return

const [startTime, endTime, playbackRate, preservesPitch] = values.split('-')

return {
endTime: parseInt(endTime, 10),
startTime: parseInt(startTime, 10),
preservesPitch: parseInt(preservesPitch, 10) == 1,
playbackRate: parseInt(parseFloat(playbackRate) / 100),
}
}

export const songState = async () => {
const state = await currentData.readTrack()
const sharedSnipState = sharedSnipValues()

if (!sharedSnipState) return { ...state, isShared: false }

const { trackId } = currentSongInfo()
const locationId = location?.pathname?.split('/')?.at(-1)

return {
trackId,
isShared: trackId == locationId,
...state,
...sharedSnipState
}
}
6 changes: 5 additions & 1 deletion src/events/listeners/action-listeners.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,21 @@ import Listeners from './listeners.js'
export default class ActionListeners extends Listeners {
constructor() {
super()
this._setup = false
}

init() {
if (this._setup) return

this.#saveSeekListener()
this.#saveTrackListener()
this.#saveSpeedListener()

this.#shareTrackListener()
this.#deleteTrackListener()
this.#resetSpeedListener()

this._setup = true
}

#resetSpeedListener() {
Expand Down Expand Up @@ -61,7 +66,6 @@ export default class ActionListeners extends Listeners {

#shareTrackListener() {
const shareButton = document.getElementById('chorus-snip-share-button')
shareButton?.removeEventListener('click', () => this.#handleShare())
shareButton?.addEventListener('click', () => this.#handleShare())
}
}
30 changes: 16 additions & 14 deletions src/events/listeners/header-listeners.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,42 +3,46 @@ import Listeners from './listeners.js'
export default class HeaderListeners extends Listeners {
constructor() {
super()
this.viewInFocus = null
this.VIEWS = ['snip', 'speed', 'seek']
this._setup = false
this._viewInFocus = null
this._VIEWS = ['snip', 'speed', 'seek']
}

init() {
if (this._setup) return

this.#snipViewToggle()
this.#seekViewToggle()
this.#speedViewToggle()
this.#closeModalListener()

this.currentView = 'snip'
this._currentView = 'snip'
this._setup = true
}

async hide() {
if (this.viewInFocus == 'speed') {
if (this._viewInFocus == 'speed') {
this._speed.clearCurrentSpeed()
await this._speed.reset()
}

this.currentView = 'snip'
this._currentView = 'snip'
this._hide()
}

#seekViewToggle() {
const seekButton = document.getElementById('chorus-seek-button')

seekButton?.addEventListener('click', async () => {
this.currentView = 'seek'
this._currentView = 'seek'
await this._seek.init()
})
}

set currentView(selectedView) {
this.viewInFocus = selectedView
set _currentView(selectedView) {
this._viewInFocus = selectedView

this.VIEWS.forEach(view => {
this._VIEWS.forEach(view => {
const viewButton = document.getElementById(`chorus-${view}-button`)
const viewInFocusContainer = document.getElementById(`chorus-${view}-controls`)
if (!viewButton || !viewInFocusContainer) return
Expand All @@ -50,21 +54,19 @@ export default class HeaderListeners extends Listeners {

#snipViewToggle() {
const snipButton = document.getElementById('chorus-snip-button')
snipButton?.addEventListener('click', () => { this.currentView = 'snip' })
snipButton?.addEventListener('click', () => { this._currentView = 'snip' })
}

#speedViewToggle() {
const speedButton = document.getElementById('chorus-speed-button')
speedButton?.addEventListener('click', async () => {
this.currentView = 'speed'
this._currentView = 'speed'
await this._speed.init()
})
}

#closeModalListener() {
const closeButton = document.getElementById('chorus-modal-close-button')
closeButton?.addEventListener('click', async () => {
await this.hide()
})
closeButton?.addEventListener('click', async () => { await this.hide() })
}
}
2 changes: 0 additions & 2 deletions src/events/listeners/listeners.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,7 @@ export default class Listeners {
}

_hide() {
this._snip.isEditing = false
const mainElement = document.getElementById('chorus-main')

mainElement.style.display = 'none'
}
}
26 changes: 17 additions & 9 deletions src/models/alert.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,35 @@ import { createAlert } from '../components/alert.js'
import { parseNodeString } from '../utils/parser.js'

export default class Alert {
constructor() {
this.init()
}

init() {
displayAlert() {
this.#setupAlert()
const alertMessage = this.#chorusAlert.querySelector('[id="chorus-alert-message"]')

alertMessage.textContent = `Snip copied to clipboard.`
this.#chorusAlert.style.display = 'flex'
setTimeout(() => {
this.#chorusAlert.style.display = 'none'
}, 3000)
}

#handleAlert({ e, target }) {
e.stopPropagation()
#handleAlert(target) {
const container = target.parentElement
container.style.display = 'none'
}

get #chorusAlert() {
return document.getElementById('chorus-alert')
}

#setupAlert() {
if (this.#chorusAlert) return

const alertEl = parseNodeString(createAlert())
document.body.appendChild(alertEl)

const closeAlertButton = document.getElementById('chorus-alert-close-button')
closeAlertButton?.addEventListener('click', (e) => {
this.#handleAlert({ e, target: closeAlertButton })
closeAlertButton?.addEventListener('click', () => {
this.#handleAlert(closeAlertButton)
})
}
}
17 changes: 14 additions & 3 deletions src/models/seek/seek-icon.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { createSeekIcon } from '../../components/seek/seek-icon.js'

import { currentData } from '../../data/current.js'
import { songState } from '../../data/song-state.js'
import { parseNodeString } from '../../utils/parser.js'
import { spotifyVideo } from '../../actions/overload.js'

Expand Down Expand Up @@ -99,13 +100,23 @@ export default class SeekIcons {
}
}

#handleSeekButton(e) {
async #calculateCurrentTime({ role, seekTime }) {
const { startTime, endTime } = await songState()
const currentTime = this.#video.currentTime
const newTimeFF = Math.min(parseInt(currentTime + seekTime, 10), parseInt(endTime, 10))
const newStartTime = currentTime < parseInt(startTime) ? 0 : startTime
const newTimeRW = Math.max(parseInt(currentTime - seekTime, 10), parseInt(newStartTime, 10))

return role == 'ff' ? newTimeFF : newTimeRW
}

async #handleSeekButton(e) {
const button = e.target
const role = button.getAttribute('role')
const seekTime = parseInt(button.firstElementChild.textContent, 10)

const currentTime = this.#video.currentTime
this.#video.currentTime = role == 'ff' ? (currentTime + seekTime) : (currentTime - seekTime)
const newTime = await this.#calculateCurrentTime({ role, seekTime })
this.#video.currentTime = { source: 'chorus', value: newTime }
}

#setupListeners() {
Expand Down
6 changes: 4 additions & 2 deletions src/models/snip/current-snip.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,11 @@ export default class CurrentSnip extends Snip {

share() {
const { url } = currentSongInfo()
const { startTime, endTime } = this.read()
const { startTime, endTime, playbackRate = '1.00', preservesPitch = true } = this.read()
const pitch = preservesPitch ? 1 : 0
const rate = parseFloat(playbackRate) * 100

const shareURL = `${url}?startTime=${startTime}&endTime=${endTime}`
const shareURL = `${url}?ch=${startTime}-${endTime}-${rate}-${pitch}`
copyToClipBoard(shareURL)

super._displayAlert()
Expand Down
Loading

0 comments on commit b2645d0

Please sign in to comment.