269 lines
6.8 KiB
TypeScript
Raw Normal View History

2018-03-11 02:00:06 +00:00
import { AnimeNotifier } from "../AnimeNotifier"
2018-03-11 03:45:01 +00:00
var audioContext: AudioContext
var audioNode: AudioBufferSourceNode
2018-03-11 17:17:35 +00:00
var gainNode: GainNode
2018-03-11 19:29:22 +00:00
var volume = 0.5
2018-03-11 22:12:21 +00:00
var volumeTimeConstant = 0.01
var volumeSmoothingDelay = 0.05
2018-03-16 16:03:59 +00:00
var targetSpeed = 1.0
2018-03-11 16:20:52 +00:00
var playId = 0
2018-03-11 04:10:32 +00:00
var audioPlayer = document.getElementById("audio-player")
2018-03-16 16:03:59 +00:00
var audioPlayerPlay = document.getElementById("audio-player-play") as HTMLButtonElement
var audioPlayerPause = document.getElementById("audio-player-pause") as HTMLButtonElement
2018-03-11 20:59:48 +00:00
var trackLink = document.getElementById("audio-player-track-title") as HTMLLinkElement
var animeInfo = document.getElementById("audio-player-anime-info") as HTMLElement
2018-03-11 22:12:21 +00:00
var animeLink = document.getElementById("audio-player-anime-link") as HTMLLinkElement
var animeImage = document.getElementById("audio-player-anime-image") as HTMLImageElement
2018-03-12 19:21:22 +00:00
var lastRequest: XMLHttpRequest
2018-03-11 03:45:01 +00:00
2018-03-11 19:22:33 +00:00
// Play audio
2018-03-11 14:43:17 +00:00
export function playAudio(arn: AnimeNotifier, element: HTMLElement) {
2018-03-11 19:22:33 +00:00
playAudioFile(arn, element.dataset.soundtrackId, element.dataset.audioSrc)
}
// Play audio file
function playAudioFile(arn: AnimeNotifier, trackId: string, trackUrl: string) {
2018-03-11 03:45:01 +00:00
if(!audioContext) {
audioContext = new AudioContext()
2018-03-11 17:17:35 +00:00
gainNode = audioContext.createGain()
2018-03-11 22:12:21 +00:00
gainNode.gain.setTargetAtTime(volume, audioContext.currentTime + volumeSmoothingDelay, volumeTimeConstant)
2018-03-11 03:45:01 +00:00
}
2018-03-11 16:20:52 +00:00
playId++
let currentPlayId = playId
2018-03-11 04:10:32 +00:00
2018-03-12 19:21:22 +00:00
if(lastRequest) {
lastRequest.abort()
lastRequest = null
}
2018-03-11 14:43:17 +00:00
// Stop current track
stopAudio(arn)
2018-03-11 19:22:33 +00:00
arn.currentSoundTrackId = trackId
arn.markPlayingSoundTrack()
2018-03-11 20:37:07 +00:00
arn.loading(true)
2018-03-11 02:00:06 +00:00
2018-03-12 19:21:22 +00:00
// Mark as loading
audioPlayer.classList.add("loading-network")
audioPlayer.classList.remove("decoding-audio")
audioPlayer.classList.remove("decoded")
2018-03-11 04:10:32 +00:00
// Request
2018-03-11 03:45:01 +00:00
let request = new XMLHttpRequest()
2018-03-11 19:22:33 +00:00
request.open("GET", trackUrl, true)
2018-03-11 03:45:01 +00:00
request.responseType = "arraybuffer"
2018-03-11 04:10:32 +00:00
2018-03-11 03:45:01 +00:00
request.onload = () => {
2018-03-11 16:20:52 +00:00
if(currentPlayId !== playId) {
return
}
2018-03-12 19:21:22 +00:00
// Mark as loading finished, now decoding starts
audioPlayer.classList.add("decoding-audio")
arn.loading(false)
2018-03-11 20:59:48 +00:00
audioContext.decodeAudioData(request.response, async buffer => {
2018-03-11 16:20:52 +00:00
if(currentPlayId !== playId) {
return
}
2018-03-12 19:21:22 +00:00
// Mark as ready
audioPlayer.classList.add("decoded")
2018-03-11 03:45:01 +00:00
audioNode = audioContext.createBufferSource()
audioNode.buffer = buffer
2018-03-11 17:17:35 +00:00
audioNode.connect(gainNode)
gainNode.connect(audioContext.destination)
2018-03-16 16:03:59 +00:00
audioNode.playbackRate.setValueAtTime(targetSpeed, 0)
2018-03-11 03:45:01 +00:00
audioNode.start(0)
2018-03-11 04:10:32 +00:00
audioNode.onended = (event: MediaStreamErrorEvent) => {
2018-03-11 16:20:52 +00:00
if(currentPlayId !== playId) {
return
2018-03-11 04:10:32 +00:00
}
2018-03-11 16:20:52 +00:00
2018-03-11 19:22:33 +00:00
playNextTrack(arn)
// stopAudio(arn)
2018-03-11 03:51:10 +00:00
}
2018-03-11 03:45:01 +00:00
}, console.error)
}
2018-03-11 04:10:32 +00:00
2018-03-11 20:37:07 +00:00
request.onerror = () => {
arn.loading(false)
}
2018-03-12 19:21:22 +00:00
lastRequest = request
2018-03-11 03:45:01 +00:00
request.send()
2018-03-12 19:21:22 +00:00
// Update track info
updateTrackInfo(trackId)
2018-03-11 03:45:01 +00:00
// Show audio player
2018-03-11 04:10:32 +00:00
audioPlayer.classList.remove("fade-out")
audioPlayerPlay.classList.add("fade-out")
audioPlayerPause.classList.remove("fade-out")
2018-03-11 03:45:01 +00:00
}
2018-03-12 19:21:22 +00:00
// Update track info
async function updateTrackInfo(trackId: string) {
// Set track title
let trackInfoResponse = await fetch("/api/soundtrack/" + trackId)
let track = await trackInfoResponse.json()
trackLink.href = "/soundtrack/" + track.id
trackLink.innerText = track.title
let animeId = ""
for(let tag of (track.tags as string[])) {
if(tag.startsWith("anime:")) {
animeId = tag.split(":")[1]
break
}
}
// Set anime info
if(animeId !== "") {
animeInfo.classList.remove("hidden")
let animeResponse = await fetch("/api/anime/" + animeId)
let anime = await animeResponse.json()
animeLink.title = anime.title.canonical
animeLink.href = "/anime/" + anime.id
animeImage.dataset.src = "//media.notify.moe/images/anime/medium/" + anime.id + anime.imageExtension
animeImage.classList.remove("hidden")
animeImage["became visible"]()
}
}
2018-03-11 14:43:17 +00:00
// Stop audio
export function stopAudio(arn: AnimeNotifier) {
arn.currentSoundTrackId = undefined
// Remove CSS class "playing"
let playingElements = document.getElementsByClassName("playing")
for(let playing of playingElements) {
playing.classList.remove("playing")
}
// Fade out sidebar player
2018-03-12 18:02:23 +00:00
// audioPlayer.classList.add("fade-out")
2018-03-11 16:20:52 +00:00
2018-03-11 20:59:48 +00:00
// Remove title
trackLink.href = ""
trackLink.innerText = ""
2018-03-11 22:12:21 +00:00
// Hide anime info
animeLink.href = ""
animeInfo.classList.add("hidden")
2018-03-11 22:12:21 +00:00
animeImage.classList.add("hidden")
2018-03-12 19:21:22 +00:00
// Show play button
audioPlayerPlay.classList.remove("fade-out")
audioPlayerPause.classList.add("fade-out")
2018-03-11 17:17:35 +00:00
if(gainNode) {
gainNode.disconnect()
2018-03-11 16:20:52 +00:00
}
2018-03-11 17:17:35 +00:00
if(audioNode) {
audioNode.stop()
audioNode.disconnect()
audioNode = null
}
2018-03-11 14:43:17 +00:00
}
// Toggle audio
export function toggleAudio(arn: AnimeNotifier, element: HTMLElement) {
2018-03-11 15:42:49 +00:00
// If we're clicking on the same track again, stop playing.
// Otherwise, start the track we clicked on.
if(arn.currentSoundTrackId && element.dataset.soundtrackId === arn.currentSoundTrackId) {
2018-03-11 14:43:17 +00:00
stopAudio(arn)
2018-03-11 15:42:49 +00:00
} else {
playAudio(arn, element)
2018-03-11 14:43:17 +00:00
}
}
2018-03-16 16:03:59 +00:00
// Play or pause audio
export function playPauseAudio(arn: AnimeNotifier) {
if(!audioNode) {
playNextTrack(arn)
return
}
if(audioNode.playbackRate.value === 0) {
resumeAudio(arn, audioPlayerPlay)
} else {
pauseAudio(arn, audioPlayerPlay)
}
}
2018-03-11 20:37:07 +00:00
// Play previous track
export async function playPreviousTrack(arn: AnimeNotifier) {
alert("Previous track is currently work in progress! Check back later :)")
}
2018-03-11 19:22:33 +00:00
// Play next track
export async function playNextTrack(arn: AnimeNotifier) {
// Get random track
let response = await fetch("/api/next/soundtrack")
let track = await response.json()
playAudioFile(arn, track.id, "https://notify.moe/audio/" + track.file)
2018-03-12 19:21:22 +00:00
// arn.statusMessage.showInfo("Now playing: " + track.title)
2018-03-11 19:22:33 +00:00
return track
}
2018-03-11 16:20:52 +00:00
// Set volume
2018-03-11 17:17:35 +00:00
export function setVolume(arn: AnimeNotifier, element: HTMLInputElement) {
volume = parseFloat(element.value) / 100.0
2018-03-11 16:20:52 +00:00
2018-03-11 17:17:35 +00:00
if(gainNode) {
2018-03-11 22:12:21 +00:00
gainNode.gain.setTargetAtTime(volume, audioContext.currentTime + volumeSmoothingDelay, volumeTimeConstant)
2018-03-11 17:17:35 +00:00
}
2018-03-11 16:20:52 +00:00
}
2018-03-11 03:45:01 +00:00
// Pause audio
export function pauseAudio(arn: AnimeNotifier, button: HTMLButtonElement) {
if(!audioNode) {
return
}
audioNode.playbackRate.setValueAtTime(0.0, 0)
2018-03-11 04:10:32 +00:00
audioPlayerPlay.classList.remove("fade-out")
audioPlayerPause.classList.add("fade-out")
2018-03-11 03:45:01 +00:00
}
// Resume audio
2018-03-11 20:37:07 +00:00
export function resumeAudio(arn: AnimeNotifier, button: HTMLButtonElement) {
2018-03-11 03:45:01 +00:00
if(!audioNode) {
2018-03-11 20:37:07 +00:00
playNextTrack(arn)
2018-03-11 03:45:01 +00:00
return
}
2018-03-16 16:03:59 +00:00
audioNode.playbackRate.setValueAtTime(targetSpeed, 0)
2018-03-11 03:45:01 +00:00
2018-03-11 04:10:32 +00:00
audioPlayerPlay.classList.add("fade-out")
audioPlayerPause.classList.remove("fade-out")
2018-03-16 16:03:59 +00:00
}
// Add speed
export function addSpeed(arn: AnimeNotifier, speed: number) {
if(!audioNode || audioNode.playbackRate.value === 0) {
return
}
targetSpeed += speed
if(targetSpeed < 0.5) {
targetSpeed = 0.5
} else if(targetSpeed > 2) {
targetSpeed = 2
}
audioNode.playbackRate.setValueAtTime(targetSpeed, 0)
arn.statusMessage.showInfo("Playback speed: " + Math.round(targetSpeed * 100) + "%")
2018-03-11 02:00:06 +00:00
}