| Current Path : /var/www/homesaver/www/bitrix/js/ui/vue3/components/audioplayer/src/ |
| Current File : /var/www/homesaver/www/bitrix/js/ui/vue3/components/audioplayer/src/audioplayer.js |
/**
* Bitrix UI
* Audio player Vue component
*
* @package bitrix
* @subpackage ui
* @copyright 2001-2021 Bitrix
*/
import 'ui.fonts.opensans';
import "./audioplayer.css";
import 'main.polyfill.intersectionobserver';
import {BitrixVue} from "ui.vue3";
import {BaseEvent, EventEmitter} from "main.core.events";
const State = Object.freeze({
play: 'play',
pause: 'pause',
stop: 'stop',
none: 'none',
});
export {State as AudioPlayerState};
export const AudioPlayer = BitrixVue.mutableComponent('ui-audioplayer',
{
props:
{
id: { default: 0 },
src: { default: '' },
autoPlayNext: { default: true },
background: { default: 'light' },
},
data()
{
return {
isDark: false,
preload: "none",
loaded: false,
loading: false,
playAfterLoad: false,
state: State.none,
progress: 0,
progressInPixel: 0,
seek: 0,
timeCurrent: 0,
timeTotal: 0,
}
},
created()
{
this.preloadRequestSent = false;
this.registeredId = 0;
this.registerPlayer(this.id);
this.$Bitrix.eventEmitter.subscribe('ui:audioplayer:play', this.onPlay);
this.$Bitrix.eventEmitter.subscribe('ui:audioplayer:stop', this.onStop);
this.$Bitrix.eventEmitter.subscribe('ui:audioplayer:pause', this.onPause);
this.$Bitrix.eventEmitter.subscribe('ui:audioplayer:preload', this.onPreload);
EventEmitter.subscribe('ui:audioplayer:pause', this.onPause);
this.isDark = this.background === 'dark';
},
mounted()
{
this.getObserver().observe(this.$refs.body);
},
beforeUnmount()
{
this.unregisterPlayer();
this.$Bitrix.eventEmitter.unsubscribe('ui:audioplayer:play', this.onPlay);
this.$Bitrix.eventEmitter.unsubscribe('ui:audioplayer:stop', this.onStop);
this.$Bitrix.eventEmitter.unsubscribe('ui:audioplayer:pause', this.onPause);
this.$Bitrix.eventEmitter.unsubscribe('ui:audioplayer:preload', this.onPreload);
EventEmitter.unsubscribe('ui:audioplayer:pause', this.onPause);
this.getObserver().unobserve(this.$refs.body);
},
watch:
{
id(value)
{
this.registerPlayer(value);
},
progress(value)
{
if (value > 70)
{
this.preloadNext();
}
},
},
methods:
{
loadFile(play = false)
{
if (this.loaded)
{
return true;
}
if (this.loading && !play)
{
return true;
}
this.preload = 'auto';
if (play)
{
this.loading = true;
if (this.source())
{
this.source().play();
}
}
return true;
},
clickToButton()
{
if (!this.src)
{
return false;
}
if (this.state === State.play)
{
this.pause();
}
else
{
this.play();
}
},
play()
{
if (!this.loaded)
{
this.loadFile(true);
return false;
}
this.source().play();
},
pause()
{
this.source().pause();
},
stop()
{
this.state = State.stop;
this.source().pause();
},
setPosition(event)
{
if (!this.loaded)
{
this.loadFile(true);
return false;
}
let pixelPerPercent = this.$refs.track.offsetWidth / 100;
this.setProgress(this.seek / pixelPerPercent, this.seek);
if (this.state !== State.play)
{
this.state = State.pause;
}
this.play();
this.source().currentTime = this.timeTotal/100*this.progress;
},
seeking(event)
{
if (!this.loaded)
{
return false;
}
this.seek = event.offsetX > 0? event.offsetX: 0;
return true;
},
setProgress(percent, pixel = -1)
{
this.progress = percent;
this.progressInPixel = pixel > 0? pixel: Math.round(this.$refs.track.offsetWidth / 100 * percent);
},
formatTime(second)
{
second = Math.floor(second);
const hour = Math.floor(second/60/60);
if (hour > 0)
{
second -= hour*60*60;
}
const minute = Math.floor(second/60);
if (minute > 0)
{
second -= minute*60;
}
return (hour > 0? hour+':': '')
+ (hour > 0? minute.toString().padStart(2, "0")+':': minute+':')
+ second.toString().padStart(2, "0")
},
registerPlayer(id)
{
if (id <= 0)
{
return false;
}
this.unregisterPlayer();
this.$Bitrix.Data.set('ui:audioplayer:id', [...new Set([...this.$Bitrix.Data.get('ui:audioplayer:id', []), id])].sort((a, b) => a - b));
this.registeredId = id;
return true;
},
unregisterPlayer()
{
if (!this.registeredId)
{
return true;
}
this.$Bitrix.Data.get('ui:audioplayer:id', this.$Bitrix.Data.get('ui:audioplayer:id', []).filter(id => id !== this.registeredId));
this.registeredId = 0;
return true;
},
playNext()
{
if (!this.registeredId || !this.autoPlayNext)
{
return false;
}
const nextId = this.$Bitrix.Data.get('ui:audioplayer:id', []).filter(id => id > this.registeredId).slice(0, 1)[0];
if (nextId)
{
this.$Bitrix.eventEmitter.emit('ui:audioplayer:play', {id: nextId, start: true});
}
return true;
},
preloadNext()
{
if (this.preloadRequestSent)
{
return true;
}
if (!this.registeredId || !this.autoPlayNext)
{
return false;
}
this.preloadRequestSent = true;
const nextId = this.$Bitrix.Data.get('ui:audioplayer:id', []).filter(id => id > this.registeredId).slice(0, 1)[0];
if (nextId)
{
this.$Bitrix.eventEmitter.emit('ui:audioplayer:preload', {id: nextId});
}
return true;
},
onPlay(event: BaseEvent)
{
const data = event.getData();
if (data.id !== this.id)
{
return false;
}
if (data.start)
{
this.stop();
}
this.play();
},
onStop(event: BaseEvent)
{
const data = event.getData();
if (data.initiator === this.id)
{
return false;
}
this.stop();
},
onPause(event: BaseEvent)
{
const data = event.getData();
if (data.initiator === this.id)
{
return false;
}
this.pause();
},
onPreload(event: BaseEvent)
{
const data = event.getData();
if (data.id !== this.id)
{
return false;
}
this.loadFile();
},
source()
{
return this.$refs.source;
},
audioEventRouter(eventName, event)
{
if (
eventName === 'durationchange'
|| eventName === 'loadeddata'
|| eventName === 'loadedmetadata'
)
{
if (!this.source())
{
return;
}
this.timeTotal = this.source().duration;
}
else if (
eventName === 'abort'
|| eventName === 'error'
)
{
console.error('BxAudioPlayer: load failed', this.id, event);
this.loading = false;
this.state = State.none;
this.timeTotal = 0;
this.preload = 'none';
}
else if (
eventName === 'canplaythrough'
)
{
this.loading = false;
this.loaded = true;
}
else if (eventName === 'timeupdate')
{
if (!this.source())
{
return;
}
this.timeCurrent = this.source().currentTime;
this.setProgress(Math.round(100/this.timeTotal*this.timeCurrent));
if (
this.state === State.play
&& this.timeCurrent >= this.timeTotal
)
{
this.playNext();
}
}
else if (eventName === 'pause')
{
if (this.state !== State.stop)
{
this.state = State.pause;
}
}
else if (eventName === 'play')
{
this.state = State.play;
if (this.state === State.stop)
{
this.progress = 0;
this.timeCurrent = 0;
}
if (this.id > 0)
{
this.$Bitrix.eventEmitter.emit('ui:audioplayer:pause', {initiator: this.id});
EventEmitter.emit('ui:audioplayer:pause', {initiator: this.id});
}
}
},
getObserver()
{
if (this.observer)
{
return this.observer;
}
this.observer = new IntersectionObserver((entries, observer) =>
{
entries.forEach((entry) =>
{
if (entry.isIntersecting)
{
if (this.preload === "none")
{
this.preload = "metadata";
this.observer.unobserve(entry.target);
}
}
});
},{
threshold: [0, 1]
});
return this.observer;
}
},
computed:
{
State: () => State,
seekPosition()
{
if (!this.loaded && !this.seek || this.isMobile)
{
return 'display: none'
}
return `left: ${this.seek}px;`;
},
progressPosition()
{
if (!this.loaded || this.state === State.none)
{
return `width: 100%;`;
}
return `width: ${this.progressInPixel}px;`;
},
labelTime()
{
if (!this.loaded && !this.timeTotal)
{
return '--:--';
}
let time;
if (this.state === State.play)
{
time = this.timeTotal - this.timeCurrent;
}
else
{
time = this.timeTotal;
}
return this.formatTime(time);
},
isMobile()
{
const UA = navigator.userAgent.toLowerCase();
return (
UA.includes('android')
|| UA.includes('iphone')
|| UA.includes('ipad')
|| UA.includes('bitrixmobile')
)
},
},
template: `
<div :class="['ui-vue-audioplayer-container', {
'ui-vue-audioplayer-container-dark': isDark,
'ui-vue-audioplayer-container-mobile': isMobile,
}]" ref="body">
<div class="ui-vue-audioplayer-controls-container">
<button :class="['ui-vue-audioplayer-control', {
'ui-vue-audioplayer-control-loader': loading,
'ui-vue-audioplayer-control-play': !loading && state !== State.play,
'ui-vue-audioplayer-control-pause': !loading && state === State.play,
}]" @click="clickToButton"></button>
</div>
<div class="ui-vue-audioplayer-timeline-container">
<div class="ui-vue-audioplayer-track-container" @click="setPosition" ref="track">
<div class="ui-vue-audioplayer-track-mask"></div>
<div class="ui-vue-audioplayer-track" :style="progressPosition"></div>
<div class="ui-vue-audioplayer-track-seek" :style="seekPosition"></div>
<div class="ui-vue-audioplayer-track-event" @mousemove="seeking"></div>
</div>
<div class="ui-vue-audioplayer-timers-container">
<div class="ui-vue-audioplayer-time-current">{{labelTime}}</div>
</div>
</div>
<audio v-if="src" :src="src" class="ui-vue-audioplayer-source" ref="source" :preload="preload"
@abort="audioEventRouter('abort', $event)"
@error="audioEventRouter('error', $event)"
@suspend="audioEventRouter('suspend', $event)"
@canplay="audioEventRouter('canplay', $event)"
@canplaythrough="audioEventRouter('canplaythrough', $event)"
@durationchange="audioEventRouter('durationchange', $event)"
@loadeddata="audioEventRouter('loadeddata', $event)"
@loadedmetadata="audioEventRouter('loadedmetadata', $event)"
@timeupdate="audioEventRouter('timeupdate', $event)"
@play="audioEventRouter('play', $event)"
@playing="audioEventRouter('playing', $event)"
@pause="audioEventRouter('pause', $event)"
></audio>
</div>
`
});