mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-05-05 05:32:02 +00:00
Fix doubled player on VHS LoadAudio in vue (#8206)
In vue mode, the VHS Load Audio (Upload) node had 2 audio previews. This occurred because the native AudioPreview widget was being applied to any combo widget with the name `audio`. This native preview does not support the advanced preview functions VHS provides like seeking to specific start time, trimming to a target duration, or converting from formats the browser may not support. This is fixed through a fairly involved cleanup to instead display the litegraph AudioUI widget as an AudioPreview widget when in vue mode. ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-8206-Fix-doubled-player-on-VHS-LoadAudio-in-vue-2ef6d73d365081ce8907dca2706214a1) by [Unito](https://www.unito.io) --------- Co-authored-by: github-actions <github-actions@github.com> Co-authored-by: Alexander Brown <drjkl@comfy.org>
This commit is contained in:
@@ -1,17 +1,13 @@
|
||||
<template>
|
||||
<div class="relative">
|
||||
<div
|
||||
v-if="!hidden"
|
||||
:class="
|
||||
cn(
|
||||
'bg-component-node-widget-background box-border flex gap-4 items-center justify-start relative rounded-lg w-full h-16 px-4 py-0',
|
||||
{ hidden: hideWhenEmpty && !hasAudio }
|
||||
)
|
||||
"
|
||||
v-if="!hideWhenEmpty || modelValue"
|
||||
class="bg-component-node-widget-background box-border flex gap-4 items-center justify-start relative rounded-lg w-full h-16 px-4 py-0"
|
||||
>
|
||||
<!-- Hidden audio element -->
|
||||
<audio
|
||||
ref="audioRef"
|
||||
:src="modelValue"
|
||||
@loadedmetadata="handleLoadedMetadata"
|
||||
@timeupdate="handleTimeUpdate"
|
||||
@ended="handleEnded"
|
||||
@@ -137,18 +133,13 @@
|
||||
<script setup lang="ts">
|
||||
import Slider from 'primevue/slider'
|
||||
import TieredMenu from 'primevue/tieredmenu'
|
||||
import { computed, nextTick, onUnmounted, ref, watch } from 'vue'
|
||||
import { computed, ref, useTemplateRef } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { whenever } from '@vueuse/core'
|
||||
|
||||
import type { LGraphNode } from '@/lib/litegraph/src/LGraphNode'
|
||||
import { api } from '@/scripts/api'
|
||||
import { app } from '@/scripts/app'
|
||||
import { useNodeOutputStore } from '@/stores/imagePreviewStore'
|
||||
import { getLocatorIdFromNodeData } from '@/utils/graphTraversalUtil'
|
||||
import { isOutputNode } from '@/utils/nodeFilterUtil'
|
||||
import { cn } from '@/utils/tailwindUtil'
|
||||
|
||||
import { formatTime, getResourceURL } from '../../utils/audioUtils'
|
||||
import { formatTime } from '../../utils/audioUtils'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
@@ -156,8 +147,6 @@ const props = withDefaults(
|
||||
defineProps<{
|
||||
hideWhenEmpty?: boolean
|
||||
showOptionsButton?: boolean
|
||||
nodeId?: string
|
||||
audioUrl?: string
|
||||
}>(),
|
||||
{
|
||||
hideWhenEmpty: true
|
||||
@@ -165,14 +154,13 @@ const props = withDefaults(
|
||||
)
|
||||
|
||||
// Refs
|
||||
const audioRef = ref<HTMLAudioElement>()
|
||||
const audioRef = useTemplateRef('audioRef')
|
||||
const optionsMenu = ref()
|
||||
const isPlaying = ref(false)
|
||||
const isMuted = ref(false)
|
||||
const volume = ref(1)
|
||||
const currentTime = ref(0)
|
||||
const duration = ref(0)
|
||||
const hasAudio = ref(false)
|
||||
const playbackRate = ref(1)
|
||||
|
||||
// Computed
|
||||
@@ -180,61 +168,11 @@ const progressPercentage = computed(() => {
|
||||
if (!duration.value || duration.value === 0) return 0
|
||||
return (currentTime.value / duration.value) * 100
|
||||
})
|
||||
const modelValue = defineModel<string>()
|
||||
|
||||
const showVolumeTwo = computed(() => !isMuted.value && volume.value > 0.5)
|
||||
const showVolumeOne = computed(() => isMuted.value && volume.value > 0)
|
||||
|
||||
const litegraphNode = computed(() => {
|
||||
if (!props.nodeId || !app.canvas.graph) return null
|
||||
return app.canvas.graph.getNodeById(props.nodeId) as LGraphNode | null
|
||||
})
|
||||
|
||||
const hidden = computed(() => {
|
||||
if (!litegraphNode.value) return false
|
||||
// dont show if its a LoadAudio and we have nodeId
|
||||
const isLoadAudio =
|
||||
litegraphNode.value.constructor?.comfyClass === 'LoadAudio'
|
||||
return isLoadAudio && !!props.nodeId
|
||||
})
|
||||
|
||||
// Check if this is an output node
|
||||
const isOutputNodeRef = computed(() => {
|
||||
const node = litegraphNode.value
|
||||
return !!node && isOutputNode(node)
|
||||
})
|
||||
|
||||
const nodeLocatorId = computed(() => {
|
||||
const node = litegraphNode.value
|
||||
if (!node) return null
|
||||
return getLocatorIdFromNodeData(node)
|
||||
})
|
||||
|
||||
const nodeOutputStore = useNodeOutputStore()
|
||||
|
||||
// Computed audio URL from node output (for output nodes)
|
||||
const audioUrlFromOutput = computed(() => {
|
||||
if (!isOutputNodeRef.value || !nodeLocatorId.value) return ''
|
||||
|
||||
const nodeOutput = nodeOutputStore.nodeOutputs[nodeLocatorId.value]
|
||||
if (!nodeOutput?.audio || nodeOutput.audio.length === 0) return ''
|
||||
|
||||
const audio = nodeOutput.audio[0]
|
||||
if (!audio.filename) return ''
|
||||
|
||||
return api.apiURL(
|
||||
getResourceURL(
|
||||
audio.subfolder || '',
|
||||
audio.filename,
|
||||
audio.type || 'output'
|
||||
)
|
||||
)
|
||||
})
|
||||
|
||||
// Combined audio URL (output takes precedence for output nodes)
|
||||
const finalAudioUrl = computed(() => {
|
||||
return audioUrlFromOutput.value || props.audioUrl || ''
|
||||
})
|
||||
|
||||
// Playback controls
|
||||
const togglePlayPause = () => {
|
||||
if (!audioRef.value || !audioRef.value.src) {
|
||||
@@ -335,36 +273,15 @@ const menuItems = computed(() => [
|
||||
}
|
||||
])
|
||||
|
||||
// Load audio from URL
|
||||
const loadAudioFromUrl = (url: string) => {
|
||||
if (!audioRef.value) return
|
||||
isPlaying.value = false
|
||||
audioRef.value.pause()
|
||||
audioRef.value.src = url
|
||||
void audioRef.value.load()
|
||||
hasAudio.value = !!url
|
||||
}
|
||||
|
||||
// Watch for finalAudioUrl changes
|
||||
watch(
|
||||
finalAudioUrl,
|
||||
(newUrl) => {
|
||||
if (newUrl) {
|
||||
void nextTick(() => {
|
||||
loadAudioFromUrl(newUrl)
|
||||
})
|
||||
}
|
||||
whenever(
|
||||
modelValue,
|
||||
() => {
|
||||
isPlaying.value = false
|
||||
audioRef.value?.pause()
|
||||
void audioRef.value?.load()
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
|
||||
// Cleanup
|
||||
onUnmounted(() => {
|
||||
if (audioRef.value) {
|
||||
audioRef.value.pause()
|
||||
audioRef.value.src = ''
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
Reference in New Issue
Block a user