Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion src/components/rightSidePanel/parameters/WidgetItem.vue
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ const favoritedWidgetsStore = useFavoritedWidgetsStore()
const isEditing = ref(false)

const widgetComponent = computed(() => {
const component = getComponent(widget.type, widget.name)
const component = getComponent(widget.type)
return component || WidgetLegacy
})

Expand Down
70 changes: 36 additions & 34 deletions src/extensions/core/uploadAudio.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { MediaRecorder as ExtendableMediaRecorder } from 'extendable-media-recorder'

import { useChainCallback } from '@/composables/functional/useChainCallback'
import { useNodeDragAndDrop } from '@/composables/node/useNodeDragAndDrop'
import { useNodeFileInput } from '@/composables/node/useNodeFileInput'
import { useNodePaste } from '@/composables/node/useNodePaste'
Expand All @@ -25,6 +24,17 @@ import { getNodeByLocatorId } from '@/utils/graphTraversalUtil'
import { api } from '../../scripts/api'
import { app } from '../../scripts/app'

function updateUIWidget(
audioUIWidget: DOMWidget<HTMLAudioElement, string>,
url: string = ''
) {
audioUIWidget.element.src = url
audioUIWidget.value = url
audioUIWidget.callback?.(url)
if (url) audioUIWidget.element.classList.remove('empty-audio-widget')
else audioUIWidget.element.classList.add('empty-audio-widget')
}

async function uploadFile(
audioWidget: IStringWidget,
audioUIWidget: DOMWidget<HTMLAudioElement, string>,
Expand Down Expand Up @@ -55,10 +65,10 @@ async function uploadFile(
}

if (updateNode) {
audioUIWidget.element.src = api.apiURL(
getResourceURL(...splitFilePath(path))
updateUIWidget(
audioUIWidget,
api.apiURL(getResourceURL(...splitFilePath(path)))
)
audioWidget.value = path

// Manually trigger the callback to update VueNodes
audioWidget.callback?.(path)
Expand Down Expand Up @@ -118,26 +128,18 @@ app.registerExtension({
const audios = output.audio
if (!audios?.length) return
const audio = audios[0]
audioUIWidget.element.src = api.apiURL(
getResourceURL(
audio.subfolder ?? '',
audio.filename ?? '',
audio.type
)
const resourceUrl = getResourceURL(
audio.subfolder ?? '',
audio.filename ?? '',
audio.type
)
audioUIWidget.element.classList.remove('empty-audio-widget')
updateUIWidget(audioUIWidget, api.apiURL(resourceUrl))
}
}

audioUIWidget.onRemove = useChainCallback(
audioUIWidget.onRemove,
() => {
if (!audioUIWidget.element) return
audioUIWidget.element.pause()
audioUIWidget.element.src = ''
audioUIWidget.element.remove()
}
)
let value = ''
audioUIWidget.options.getValue = () => value
audioUIWidget.options.setValue = (v) => (value = v)
Comment on lines +140 to +142

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems sus...

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed. I'm real unhappy with the setup, but the existing DOMwidget code has already defined widget.value. It's impossible to set widget.value on a DOMWidget unless you set up a getter and setter like this.


return { widget: audioUIWidget }
}
Expand All @@ -156,10 +158,12 @@ app.registerExtension({
(w) => w.name === 'audioUI'
) as unknown as DOMWidget<HTMLAudioElement, string>
const audio = output.audio[0]
audioUIWidget.element.src = api.apiURL(
getResourceURL(audio.subfolder ?? '', audio.filename ?? '', audio.type)
const resourceUrl = getResourceURL(
audio.subfolder ?? '',
audio.filename ?? '',
audio.type
)
audioUIWidget.element.classList.remove('empty-audio-widget')
updateUIWidget(audioUIWidget, api.apiURL(resourceUrl))
Comment thread
AustinMroz marked this conversation as resolved.
}
}
})
Expand All @@ -183,28 +187,26 @@ app.registerExtension({
const audioUIWidget = node.widgets.find(
(w) => w.name === 'audioUI'
) as unknown as DOMWidget<HTMLAudioElement, string>
audioUIWidget.options.canvasOnly = true

const onAudioWidgetUpdate = () => {
if (typeof audioWidget.value !== 'string') return
audioUIWidget.element.src = api.apiURL(
getResourceURL(...splitFilePath(audioWidget.value))
updateUIWidget(
audioUIWidget,
api.apiURL(
getResourceURL(...splitFilePath(audioWidget.value ?? ''))
)
)
}
// Initially load default audio file to audioUIWidget.
if (audioWidget.value) {
onAudioWidgetUpdate()
}
onAudioWidgetUpdate()

audioWidget.callback = onAudioWidgetUpdate

// Load saved audio file widget values if restoring from workflow
const onGraphConfigured = node.onGraphConfigured
node.onGraphConfigured = function () {
// @ts-expect-error fixme ts strict error
onGraphConfigured?.apply(this, arguments)
if (audioWidget.value) {
onAudioWidgetUpdate()
}
onAudioWidgetUpdate()
}

const handleUpload = async (files: File[]) => {
Expand Down Expand Up @@ -328,7 +330,7 @@ app.registerExtension({
URL.revokeObjectURL(audioUIWidget.element.src)
}

audioUIWidget.element.src = URL.createObjectURL(audioBlob)
updateUIWidget(audioUIWidget, URL.createObjectURL(audioBlob))

isRecording = false

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ const processedWidgets = computed((): ProcessedWidget[] => {
if (!shouldRenderAsVue(widget)) continue

const vueComponent =
getComponent(widget.type, widget.name) ||
getComponent(widget.type) ||
(widget.isDOMWidget ? WidgetDOM : WidgetLegacy)

const { slotMetadata, options } = widget
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -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"
Expand Down Expand Up @@ -137,104 +133,46 @@
<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()

const props = withDefaults(
defineProps<{
hideWhenEmpty?: boolean
showOptionsButton?: boolean
nodeId?: string
audioUrl?: string
}>(),
{
hideWhenEmpty: true
}
)

// 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
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)
Comment thread
AustinMroz marked this conversation as resolved.

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) {
Expand Down Expand Up @@ -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>
Expand Down
Loading