Your IP : 216.73.216.86


Current Path : /var/www/homesaver/www/bitrix/js/ui/vue3/components/audioplayer/src/
Upload File :
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>
	`
});