diff --git a/src/generators/dom/visitors/Element/Binding.ts b/src/generators/dom/visitors/Element/Binding.ts deleted file mode 100644 index e39f8c6609..0000000000 --- a/src/generators/dom/visitors/Element/Binding.ts +++ /dev/null @@ -1,348 +0,0 @@ -import deindent from '../../../../utils/deindent'; -import flattenReference from '../../../../utils/flattenReference'; -import getStaticAttributeValue from '../../../../utils/getStaticAttributeValue'; -import { DomGenerator } from '../../index'; -import Block from '../../Block'; -import { Node } from '../../../../interfaces'; -import { State } from '../../interfaces'; -import getObject from '../../../../utils/getObject'; -import getTailSnippet from '../../../../utils/getTailSnippet'; - -const readOnlyMediaAttributes = new Set([ - 'duration', - 'buffered', - 'seekable', - 'played' -]); - -export default function visitBinding( - generator: DomGenerator, - block: Block, - state: State, - node: Node, - attribute: Node -) { - const { name } = getObject(attribute.value); - const { snippet, contexts, dependencies } = block.contextualise( - attribute.value - ); - - contexts.forEach(context => { - if (!~state.allUsedContexts.indexOf(context)) - state.allUsedContexts.push(context); - }); - - const eventNames = getBindingEventName(node, attribute); - const handler = block.getUniqueName( - `${state.parentNode}_${eventNames.join('_')}_handler` - ); - const isMultipleSelect = - node.name === 'select' && - node.attributes.find( - (attr: Node) => attr.name.toLowerCase() === 'multiple' - ); // TODO use getStaticAttributeValue - const type = getStaticAttributeValue(node, 'type'); - const bindingGroup = attribute.name === 'group' - ? getBindingGroup(generator, attribute.value) - : null; - - const isMediaElement = node.name === 'audio' || node.name === 'video'; - const isReadOnly = isMediaElement && readOnlyMediaAttributes.has(attribute.name) - - const value = getBindingValue( - generator, - block, - state, - node, - attribute, - isMultipleSelect, - isMediaElement, - bindingGroup, - type - ); - - let setter = getSetter(generator, block, name, snippet, state.parentNode, attribute, dependencies, value); - let updateElement = `${state.parentNode}.${attribute.name} = ${snippet};`; - - const needsLock = !isReadOnly && node.name !== 'input' || !/radio|checkbox|range|color/.test(type); // TODO others? - const lock = `#${state.parentNode}_updating`; - let updateConditions = needsLock ? [`!${lock}`] : []; - - if (needsLock) block.addVariable(lock, 'false'); - - // special case - if (type === 'radio') { - setter = deindent` - if (!${state.parentNode}.checked) return; - ${setter} - `; - } - - const condition = type === 'checkbox' - ? `~${snippet}.indexOf(${state.parentNode}.__value)` - : `${state.parentNode}.__value === ${snippet}`; - - block.builders.hydrate.addLine( - `#component._bindingGroups[${bindingGroup}].push(${state.parentNode});` - ); - - block.builders.destroy.addBlock( - `#component._bindingGroups[${bindingGroup}].splice(#component._bindingGroups[${bindingGroup}].indexOf(${state.parentNode}), 1);` - ); - - updateElement = `${state.parentNode}.checked = ${condition};`; - } else if (isMediaElement) { - generator.hasComplexBindings = true; - block.builders.hydrate.addBlock(`#component._root._beforecreate.push(${handler});`); - - if (attribute.name === 'currentTime') { - const frame = block.getUniqueName(`${state.parentNode}_animationframe`); - block.addVariable(frame); - setter = deindent` - cancelAnimationFrame(${frame}); - if (!${state.parentNode}.paused) ${frame} = requestAnimationFrame(${handler}); - ${setter} - `; - - updateConditions.push(`!isNaN(${snippet})`); - } else if (attribute.name === 'paused') { - // this is necessary to prevent the audio restarting by itself - const last = block.getUniqueName(`${state.parentNode}_paused_value`); - block.addVariable(last, 'true'); - - updateConditions = [`${last} !== (${last} = ${snippet})`]; - updateElement = `${state.parentNode}[${last} ? "pause" : "play"]();`; - } - } - - block.builders.init.addBlock(deindent` - function ${handler}() { - ${needsLock && `${lock} = true;`} - ${setter} - ${needsLock && `${lock} = false;`} - } - `); - - if (node.name === 'input' && type === 'range') { - // need to bind to `input` and `change`, for the benefit of IE - block.builders.hydrate.addBlock(deindent` - @addListener(${state.parentNode}, "input", ${handler}); - @addListener(${state.parentNode}, "change", ${handler}); - `); - - block.builders.destroy.addBlock(deindent` - @removeListener(${state.parentNode}, "input", ${handler}); - @removeListener(${state.parentNode}, "change", ${handler}); - `); - } else { - eventNames.forEach(eventName => { - block.builders.hydrate.addLine( - `@addListener(${state.parentNode}, "${eventName}", ${handler});` - ); - - block.builders.destroy.addLine( - `@removeListener(${state.parentNode}, "${eventName}", ${handler});` - ); - }); - } - - if (!isMediaElement) { - node.initialUpdate = updateElement; - } - - if (!isReadOnly) { // audio/video duration is read-only, it never updates - if (updateConditions.length) { - block.builders.update.addBlock(deindent` - if (${updateConditions.join(' && ')}) { - ${updateElement} - } - `); - } else { - block.builders.update.addBlock(deindent` - ${updateElement} - `); - } - } - - if (attribute.name === 'paused') { - block.builders.create.addLine( - `@addListener(${state.parentNode}, "play", ${handler});` - ); - block.builders.destroy.addLine( - `@removeListener(${state.parentNode}, "play", ${handler});` - ); - } -} - -function getBindingEventName(node: Node, attribute: Node) { - if (node.name === 'input') { - const typeAttribute = node.attributes.find( - (attr: Node) => attr.type === 'Attribute' && attr.name === 'type' - ); - const type = typeAttribute ? typeAttribute.value[0].data : 'text'; // TODO in validation, should throw if type attribute is not static - - return [type === 'checkbox' || type === 'radio' ? 'change' : 'input']; - } - - if (node.name === 'textarea') return ['input']; - if (attribute.name === 'currentTime') return ['timeupdate']; - if (attribute.name === 'duration') return ['durationchange']; - if (attribute.name === 'paused') return ['pause']; - if (attribute.name === 'buffered') return ['progress', 'loadedmetadata']; - if (attribute.name === 'seekable') return ['loadedmetadata']; - if (attribute.name === 'played') return ['timeupdate']; - - return ['change']; -} - -function getBindingValue( - generator: DomGenerator, - block: Block, - state: State, - node: Node, - attribute: Node, - isMultipleSelect: boolean, - isMediaElement: boolean, - bindingGroup: number, - type: string -) { - // - if (type === 'range' || type === 'number') { - return `@toNumber(${state.parentNode}.${attribute.name})`; - } - - if (isMediaElement && (attribute.name === 'buffered' || attribute.name === 'seekable' || attribute.name === 'played')) { - return `@timeRangesToArray(${state.parentNode}.${attribute.name})` - } - - // everything else - return `${state.parentNode}.${attribute.name}`; -} - -function getBindingGroup(generator: DomGenerator, value: Node) { - const { parts } = flattenReference(value); // TODO handle cases involving computed member expressions - const keypath = parts.join('.'); - - // TODO handle contextual bindings — `keypath` should include unique ID of - // each block that provides context - let index = generator.bindingGroups.indexOf(keypath); - if (index === -1) { - index = generator.bindingGroups.length; - generator.bindingGroups.push(keypath); - } - - return index; -} - -function getSetter( - generator: DomGenerator, - block: Block, - name: string, - snippet: string, - _this: string, - attribute: Node, - dependencies: string[], - value: string, -) { - const tail = attribute.value.type === 'MemberExpression' - ? getTailSnippet(attribute.value) - : ''; - - if (block.contexts.has(name)) { - const prop = dependencies[0]; - const computed = isComputed(attribute.value); - - return deindent` - var list = ${_this}._svelte.${block.listNames.get(name)}; - var index = ${_this}._svelte.${block.indexNames.get(name)}; - ${computed && `var state = #component.get();`} - list[index]${tail} = ${value}; - - ${computed - ? `#component.set({${dependencies.map((prop: string) => `${prop}: state.${prop}`).join(', ')} });` - : `#component.set({${dependencies.map((prop: string) => `${prop}: #component.get('${prop}')`).join(', ')} });`} - `; - } - - if (attribute.value.type === 'MemberExpression') { - // This is a little confusing, and should probably be tidied up - // at some point. It addresses a tricky bug (#893), wherein - // Svelte tries to `set()` a computed property, which throws an - // error in dev mode. a) it's possible that we should be - // replacing computations with *their* dependencies, and b) - // we should probably populate `generator.readonly` sooner so - // that we don't have to do the `.some()` here - dependencies = dependencies.filter(prop => !generator.computations.some(computation => computation.key === prop)); - - return deindent` - var state = #component.get(); - ${snippet} = ${value}; - #component.set({ ${dependencies.map((prop: string) => `${prop}: state.${prop}`).join(', ')} }); - `; - } - - return `#component.set({ ${name}: ${value} });`; -} - -function isComputed(node: Node) { - while (node.type === 'MemberExpression') { - if (node.computed) return true; - node = node.object; - } - - return false; -} diff --git a/src/generators/dom/visitors/Element/Element.ts b/src/generators/dom/visitors/Element/Element.ts index d3da9e38c2..8de38c766d 100644 --- a/src/generators/dom/visitors/Element/Element.ts +++ b/src/generators/dom/visitors/Element/Element.ts @@ -4,7 +4,6 @@ import visitSlot from '../Slot'; import visitComponent from '../Component'; import visitWindow from './meta/Window'; import visitAttribute from './Attribute'; -import visitBinding from './Binding'; import addBindings from './addBindings'; import flattenReference from '../../../../utils/flattenReference'; import validCalleeObjects from '../../../../utils/validCalleeObjects'; diff --git a/src/generators/dom/visitors/Element/addBindings.ts b/src/generators/dom/visitors/Element/addBindings.ts index 161d04ae14..026541bbf0 100644 --- a/src/generators/dom/visitors/Element/addBindings.ts +++ b/src/generators/dom/visitors/Element/addBindings.ts @@ -8,7 +8,6 @@ import { State } from '../../interfaces'; import getObject from '../../../../utils/getObject'; import getTailSnippet from '../../../../utils/getTailSnippet'; import stringifyProps from '../../../../utils/stringifyProps'; -import visitBinding from './Binding'; import { generateRule } from '../../../../shared/index'; import flatten from '../../../../utils/flattenReference'; @@ -23,19 +22,19 @@ const readOnlyMediaAttributes = new Set([ 'played' ]); -function isMediaNode(node: Node) { - return node.name === 'audio' || node.name === 'video'; +function isMediaNode(name: string) { + return name === 'audio' || name === 'video'; } const events = [ { - name: 'input', + eventNames: ['input'], filter: (node: Node, binding: Binding) => node.name === 'textarea' || node.name === 'input' && !/radio|checkbox/.test(getStaticAttributeValue(node, 'type')) }, { - name: 'change', + eventNames: ['change'], filter: (node: Node, binding: Binding) => node.name === 'select' || node.name === 'input' && /radio|checkbox|range/.test(getStaticAttributeValue(node, 'type')) @@ -43,31 +42,31 @@ const events = [ // media events { - name: 'timeupdate', + eventNames: ['timeupdate'], filter: (node: Node, binding: Binding) => isMediaNode(node.name) && (binding.name === 'currentTime' || binding.name === 'played') }, { - name: 'durationchange', + eventNames: ['durationchange'], filter: (node: Node, binding: Binding) => isMediaNode(node.name) && binding.name === 'duration' }, { - name: 'pause', + eventNames: ['play', 'pause'], filter: (node: Node, binding: Binding) => isMediaNode(node.name) && binding.name === 'paused' }, { - name: 'progress', + eventNames: ['progress'], filter: (node: Node, binding: Binding) => isMediaNode(node.name) && binding.name === 'buffered' }, { - name: 'loadedmetadata', + eventNames: ['loadedmetadata'], filter: (node: Node, binding: Binding) => isMediaNode(node.name) && (binding.name === 'buffered' || binding.name === 'seekable') @@ -90,6 +89,8 @@ export default function addBindings( const mungedBindings = bindings.map(binding => { const isReadOnly = isMediaNode(node.name) && readOnlyMediaAttributes.has(binding.name); + let updateCondition: string; + const { name } = getObject(binding.value); const { snippet, contexts, dependencies } = block.contextualise( binding.value @@ -114,7 +115,7 @@ export default function addBindings( getStaticAttributeValue(node, 'type') ); - const viewToModel = getSetter( + const viewToModel = getViewToModel( generator, block, name, @@ -126,7 +127,8 @@ export default function addBindings( ); // model to view - const modelToView = getUpdater(node, binding, snippet); + let modelToView = getModelToView(node, binding, snippet); + let initialUpdate = modelToView; // block.builders.update.addLine(modelToView); // special cases @@ -137,40 +139,67 @@ export default function addBindings( `#component._bindingGroups[${bindingGroup}].push(${node.var});` ); - block.builders.destroy.addBlock( + block.builders.destroy.addLine( `#component._bindingGroups[${bindingGroup}].splice(#component._bindingGroups[${bindingGroup}].indexOf(${node.var}), 1);` ); } + if (binding.name === 'currentTime') { + updateCondition = `!isNaN(${snippet})`; + initialUpdate = null; + } + + if (binding.name === 'paused') { + // this is necessary to prevent the audio restarting by itself + const last = block.getUniqueName(`${node.var}_is_paused`); + block.addVariable(last, 'true'); + + updateCondition = `${last} !== (${last} = ${snippet})`; + modelToView = `${node.var}[${last} ? "pause" : "play"]();`; + initialUpdate = null; + } + return { name: binding.name, object: name, viewToModel, modelToView, - needsLock: !isReadOnly && needsLock + initialUpdate, + needsLock: !isReadOnly && needsLock, + updateCondition }; }); + const lock = mungedBindings.some(binding => binding.needsLock) ? + block.getUniqueName(`${node.var}_updating`) : + null; + + if (lock) block.addVariable(lock, 'false'); + const groups = events .map(event => { return { - name: event.name, + events: event.eventNames, bindings: mungedBindings.filter(binding => event.filter(node, binding)) }; }) .filter(group => group.bindings.length); groups.forEach(group => { - const handler = block.getUniqueName(`${node.var}_${group.name}_handler`); + const handler = block.getUniqueName(`${node.var}_${group.events.join('_')}_handler`); - const needsLock = group.bindings.some(binding => binding.needsLock); + const needsLock = group.bindings.some(binding => binding.needsLock); - const lock = needsLock ? block.getUniqueName(`${node.var}_updating`) : null; - if (needsLock) block.addVariable(lock, 'false'); + group.bindings.forEach(binding => { + if (!binding.modelToView) return; - block.builders.update.addBlock(deindent` - ${group.bindings.map(binding => needsLock ? `if (!${lock}) ${binding.modelToView}` : binding.modelToView)} - `); + const updateConditions = needsLock ? [`!${lock}`] : []; + if (binding.updateCondition) updateConditions.push(binding.updateCondition); + + block.builders.update.addLine( + updateConditions.length ? `if (${updateConditions.join(' && ')}) ${binding.modelToView}` : binding.modelToView + ); + }); const usesContext = group.bindings.some(binding => binding.viewToModel.usesContext); const usesState = group.bindings.some(binding => binding.viewToModel.usesState); @@ -183,8 +212,22 @@ export default function addBindings( }); }); // TODO use stringifyProps here, once indenting is fixed + // media bindings — awkward special case. The native timeupdate events + // fire too infrequently, so we need to take matters into our + // own hands + let animation_frame; + if (group.bindings.find(binding => binding.name === 'currentTime')) { + animation_frame = block.getUniqueName(`${node.var}_animationframe`); + block.addVariable(animation_frame); + } + block.builders.init.addBlock(deindent` function ${handler}() { + ${ + animation_frame && deindent` + cancelAnimationFrame(${animation_frame}); + if (!${node.var}.paused) ${animation_frame} = requestAnimationFrame(${handler});` + } ${usesContext && `var context = ${node.var}._svelte;`} ${usesState && `var state = #component.get();`} ${needsLock && `${lock} = true;`} @@ -194,45 +237,47 @@ export default function addBindings( } `); - block.builders.hydrate.addLine( - `@addListener(${node.var}, "${group.name}", ${handler});` - ); + group.events.forEach(name => { + block.builders.hydrate.addLine( + `@addListener(${node.var}, "${name}", ${handler});` + ); - block.builders.destroy.addLine( - `@removeListener(${node.var}, "${group.name}", ${handler});` - ); + block.builders.destroy.addLine( + `@removeListener(${node.var}, "${name}", ${handler});` + ); + }); const allInitialStateIsDefined = group.bindings .map(binding => `'${binding.object}' in state`) .join(' && '); - if (node.name === 'select' || group.bindings.find(binding => binding.name === 'indeterminate')) { + if (node.name === 'select' || group.bindings.find(binding => binding.name === 'indeterminate' || readOnlyMediaAttributes.has(binding.name))) { generator.hasComplexBindings = true; - block.builders.hydrate.addBlock( + block.builders.hydrate.addLine( `if (!(${allInitialStateIsDefined})) #component._root._beforecreate.push(${handler});` ); } }); - node.initialUpdate = mungedBindings.map(binding => binding.modelToView).join('\n'); + node.initialUpdate = mungedBindings.map(binding => binding.initialUpdate).filter(Boolean).join('\n'); } -function getUpdater( +function getModelToView( node: Node, binding: Node, snippet: string ) { + if (readOnlyMediaAttributes.has(binding.name)) { + return null; + } + if (node.name === 'select') { return getStaticAttributeValue(node, 'multiple') === true ? `@selectOptions(${node.var}, ${snippet})` : `@selectOption(${node.var}, ${snippet})`; } - if (isMediaNode(node.name)) { - throw new Error('TODO'); - } - if (binding.name === 'group') { const type = getStaticAttributeValue(node, 'type'); @@ -261,7 +306,7 @@ function getBindingGroup(generator: DomGenerator, value: Node) { return index; } -function getSetter( +function getViewToModel( generator: DomGenerator, block: Block, name: string, diff --git a/test/js/samples/media-bindings/expected-bundle.js b/test/js/samples/media-bindings/expected-bundle.js index 92eb665731..80545b9402 100644 --- a/test/js/samples/media-bindings/expected-bundle.js +++ b/test/js/samples/media-bindings/expected-bundle.js @@ -197,78 +197,51 @@ var proto = { /* generated by Svelte vX.Y.Z */ function create_main_fragment(state, component) { - var audio, audio_updating = false, audio_animationframe, audio_paused_value = true; - - function audio_progress_loadedmetadata_handler() { - audio_updating = true; - component.set({ buffered: timeRangesToArray(audio.buffered) }); - audio_updating = false; - } - - function audio_loadedmetadata_handler() { - audio_updating = true; - component.set({ seekable: timeRangesToArray(audio.seekable) }); - audio_updating = false; - } + var audio, audio_is_paused = true, audio_updating = false, audio_animationframe; function audio_timeupdate_handler() { - audio_updating = true; - component.set({ played: timeRangesToArray(audio.played) }); - audio_updating = false; - } - - function audio_timeupdate_handler_1() { - audio_updating = true; cancelAnimationFrame(audio_animationframe); - if (!audio.paused) audio_animationframe = requestAnimationFrame(audio_timeupdate_handler_1); - component.set({ currentTime: audio.currentTime }); + if (!audio.paused) audio_animationframe = requestAnimationFrame(audio_timeupdate_handler); + audio_updating = true; + component.set({ played: timeRangesToArray(audio.played), currentTime: audio.currentTime }); audio_updating = false; } function audio_durationchange_handler() { - audio_updating = true; component.set({ duration: audio.duration }); - audio_updating = false; } - function audio_pause_handler() { + function audio_play_pause_handler() { audio_updating = true; component.set({ paused: audio.paused }); audio_updating = false; } + function audio_progress_handler() { + component.set({ buffered: timeRangesToArray(audio.buffered) }); + } + + function audio_loadedmetadata_handler() { + component.set({ buffered: timeRangesToArray(audio.buffered), seekable: timeRangesToArray(audio.seekable) }); + } + return { c: function create() { audio = createElement("audio"); - addListener(audio, "play", audio_pause_handler); this.h(); }, h: function hydrate() { - component._root._beforecreate.push(audio_progress_loadedmetadata_handler); - - addListener(audio, "progress", audio_progress_loadedmetadata_handler); - addListener(audio, "loadedmetadata", audio_progress_loadedmetadata_handler); - - component._root._beforecreate.push(audio_loadedmetadata_handler); - - addListener(audio, "loadedmetadata", audio_loadedmetadata_handler); - - component._root._beforecreate.push(audio_timeupdate_handler); - addListener(audio, "timeupdate", audio_timeupdate_handler); - - component._root._beforecreate.push(audio_timeupdate_handler_1); - - addListener(audio, "timeupdate", audio_timeupdate_handler_1); - - component._root._beforecreate.push(audio_durationchange_handler); - + if (!('played' in state && 'currentTime' in state)) component._root._beforecreate.push(audio_timeupdate_handler); addListener(audio, "durationchange", audio_durationchange_handler); - - component._root._beforecreate.push(audio_pause_handler); - - addListener(audio, "pause", audio_pause_handler); + if (!('duration' in state)) component._root._beforecreate.push(audio_durationchange_handler); + addListener(audio, "play", audio_play_pause_handler); + addListener(audio, "pause", audio_play_pause_handler); + addListener(audio, "progress", audio_progress_handler); + if (!('buffered' in state)) component._root._beforecreate.push(audio_progress_handler); + addListener(audio, "loadedmetadata", audio_loadedmetadata_handler); + if (!('buffered' in state && 'seekable' in state)) component._root._beforecreate.push(audio_loadedmetadata_handler); }, m: function mount(target, anchor) { @@ -276,13 +249,8 @@ function create_main_fragment(state, component) { }, p: function update(changed, state) { - if (!audio_updating && !isNaN(state.currentTime )) { - audio.currentTime = state.currentTime ; - } - - if (audio_paused_value !== (audio_paused_value = state.paused)) { - audio[audio_paused_value ? "pause" : "play"](); - } + if (!audio_updating && !isNaN(state.currentTime )) audio.currentTime = state.currentTime ; + if (!audio_updating && audio_is_paused !== (audio_is_paused = state.paused)) audio[audio_is_paused ? "pause" : "play"](); }, u: function unmount() { @@ -290,14 +258,12 @@ function create_main_fragment(state, component) { }, d: function destroy$$1() { - removeListener(audio, "progress", audio_progress_loadedmetadata_handler); - removeListener(audio, "loadedmetadata", audio_progress_loadedmetadata_handler); - removeListener(audio, "loadedmetadata", audio_loadedmetadata_handler); removeListener(audio, "timeupdate", audio_timeupdate_handler); - removeListener(audio, "timeupdate", audio_timeupdate_handler_1); removeListener(audio, "durationchange", audio_durationchange_handler); - removeListener(audio, "pause", audio_pause_handler); - removeListener(audio, "play", audio_pause_handler); + removeListener(audio, "play", audio_play_pause_handler); + removeListener(audio, "pause", audio_play_pause_handler); + removeListener(audio, "progress", audio_progress_handler); + removeListener(audio, "loadedmetadata", audio_loadedmetadata_handler); } }; } diff --git a/test/js/samples/media-bindings/expected.js b/test/js/samples/media-bindings/expected.js index bcab29fb75..c14eb2aa83 100644 --- a/test/js/samples/media-bindings/expected.js +++ b/test/js/samples/media-bindings/expected.js @@ -2,78 +2,51 @@ import { addListener, assign, callAll, createElement, detachNode, init, insertNode, proto, removeListener, timeRangesToArray } from "svelte/shared.js"; function create_main_fragment(state, component) { - var audio, audio_updating = false, audio_animationframe, audio_paused_value = true; - - function audio_progress_loadedmetadata_handler() { - audio_updating = true; - component.set({ buffered: timeRangesToArray(audio.buffered) }); - audio_updating = false; - } - - function audio_loadedmetadata_handler() { - audio_updating = true; - component.set({ seekable: timeRangesToArray(audio.seekable) }); - audio_updating = false; - } + var audio, audio_is_paused = true, audio_updating = false, audio_animationframe; function audio_timeupdate_handler() { - audio_updating = true; - component.set({ played: timeRangesToArray(audio.played) }); - audio_updating = false; - } - - function audio_timeupdate_handler_1() { - audio_updating = true; cancelAnimationFrame(audio_animationframe); - if (!audio.paused) audio_animationframe = requestAnimationFrame(audio_timeupdate_handler_1); - component.set({ currentTime: audio.currentTime }); + if (!audio.paused) audio_animationframe = requestAnimationFrame(audio_timeupdate_handler); + audio_updating = true; + component.set({ played: timeRangesToArray(audio.played), currentTime: audio.currentTime }); audio_updating = false; } function audio_durationchange_handler() { - audio_updating = true; component.set({ duration: audio.duration }); - audio_updating = false; } - function audio_pause_handler() { + function audio_play_pause_handler() { audio_updating = true; component.set({ paused: audio.paused }); audio_updating = false; } + function audio_progress_handler() { + component.set({ buffered: timeRangesToArray(audio.buffered) }); + } + + function audio_loadedmetadata_handler() { + component.set({ buffered: timeRangesToArray(audio.buffered), seekable: timeRangesToArray(audio.seekable) }); + } + return { c: function create() { audio = createElement("audio"); - addListener(audio, "play", audio_pause_handler); this.h(); }, h: function hydrate() { - component._root._beforecreate.push(audio_progress_loadedmetadata_handler); - - addListener(audio, "progress", audio_progress_loadedmetadata_handler); - addListener(audio, "loadedmetadata", audio_progress_loadedmetadata_handler); - - component._root._beforecreate.push(audio_loadedmetadata_handler); - - addListener(audio, "loadedmetadata", audio_loadedmetadata_handler); - - component._root._beforecreate.push(audio_timeupdate_handler); - addListener(audio, "timeupdate", audio_timeupdate_handler); - - component._root._beforecreate.push(audio_timeupdate_handler_1); - - addListener(audio, "timeupdate", audio_timeupdate_handler_1); - - component._root._beforecreate.push(audio_durationchange_handler); - + if (!('played' in state && 'currentTime' in state)) component._root._beforecreate.push(audio_timeupdate_handler); addListener(audio, "durationchange", audio_durationchange_handler); - - component._root._beforecreate.push(audio_pause_handler); - - addListener(audio, "pause", audio_pause_handler); + if (!('duration' in state)) component._root._beforecreate.push(audio_durationchange_handler); + addListener(audio, "play", audio_play_pause_handler); + addListener(audio, "pause", audio_play_pause_handler); + addListener(audio, "progress", audio_progress_handler); + if (!('buffered' in state)) component._root._beforecreate.push(audio_progress_handler); + addListener(audio, "loadedmetadata", audio_loadedmetadata_handler); + if (!('buffered' in state && 'seekable' in state)) component._root._beforecreate.push(audio_loadedmetadata_handler); }, m: function mount(target, anchor) { @@ -81,13 +54,8 @@ function create_main_fragment(state, component) { }, p: function update(changed, state) { - if (!audio_updating && !isNaN(state.currentTime )) { - audio.currentTime = state.currentTime ; - } - - if (audio_paused_value !== (audio_paused_value = state.paused)) { - audio[audio_paused_value ? "pause" : "play"](); - } + if (!audio_updating && !isNaN(state.currentTime )) audio.currentTime = state.currentTime ; + if (!audio_updating && audio_is_paused !== (audio_is_paused = state.paused)) audio[audio_is_paused ? "pause" : "play"](); }, u: function unmount() { @@ -95,14 +63,12 @@ function create_main_fragment(state, component) { }, d: function destroy() { - removeListener(audio, "progress", audio_progress_loadedmetadata_handler); - removeListener(audio, "loadedmetadata", audio_progress_loadedmetadata_handler); - removeListener(audio, "loadedmetadata", audio_loadedmetadata_handler); removeListener(audio, "timeupdate", audio_timeupdate_handler); - removeListener(audio, "timeupdate", audio_timeupdate_handler_1); removeListener(audio, "durationchange", audio_durationchange_handler); - removeListener(audio, "pause", audio_pause_handler); - removeListener(audio, "play", audio_pause_handler); + removeListener(audio, "play", audio_play_pause_handler); + removeListener(audio, "pause", audio_play_pause_handler); + removeListener(audio, "progress", audio_progress_handler); + removeListener(audio, "loadedmetadata", audio_loadedmetadata_handler); } }; }