Eduard Kuzmenko
3 years ago
19 changed files with 591 additions and 380 deletions
@ -0,0 +1,180 @@ |
|||||||
|
/* |
||||||
|
* https://github.com/morethanwords/tweb
|
||||||
|
* Copyright (C) 2019-2021 Eduard Kuzmenko |
||||||
|
* https://github.com/morethanwords/tweb/blob/master/LICENSE
|
||||||
|
*/ |
||||||
|
|
||||||
|
import { GrabEvent } from "../helpers/dom/attachGrabListeners"; |
||||||
|
import appMediaPlaybackController from "./appMediaPlaybackController"; |
||||||
|
import RangeSelector from "./rangeSelector"; |
||||||
|
|
||||||
|
export default class MediaProgressLine extends RangeSelector { |
||||||
|
protected filledLoad: HTMLDivElement; |
||||||
|
|
||||||
|
protected progressRAF = 0; |
||||||
|
|
||||||
|
protected media: HTMLMediaElement; |
||||||
|
protected streamable: boolean; |
||||||
|
|
||||||
|
constructor(media?: HTMLAudioElement | HTMLVideoElement, streamable?: boolean, withTransition?: boolean, useTransform?: boolean) { |
||||||
|
super({ |
||||||
|
step: 1000 / 60 / 1000, |
||||||
|
min: 0, |
||||||
|
max: 1, |
||||||
|
withTransition, |
||||||
|
useTransform |
||||||
|
}, 0); |
||||||
|
|
||||||
|
if(media) { |
||||||
|
this.setMedia(media, streamable); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public setMedia(media: HTMLMediaElement, streamable = false) { |
||||||
|
if(this.media) { |
||||||
|
this.removeListeners(); |
||||||
|
} |
||||||
|
|
||||||
|
if(streamable && !this.filledLoad) { |
||||||
|
this.filledLoad = document.createElement('div'); |
||||||
|
this.filledLoad.classList.add('progress-line__filled', 'progress-line__loaded'); |
||||||
|
this.container.prepend(this.filledLoad); |
||||||
|
//this.setLoadProgress();
|
||||||
|
} else if(this.filledLoad) { |
||||||
|
this.filledLoad.classList.toggle('hide', !streamable); |
||||||
|
} |
||||||
|
|
||||||
|
this.media = media; |
||||||
|
this.streamable = streamable; |
||||||
|
if(!media.paused || media.currentTime > 0) { |
||||||
|
this.onPlay(); |
||||||
|
} |
||||||
|
|
||||||
|
let wasPlaying = false; |
||||||
|
this.setSeekMax(); |
||||||
|
this.setListeners(); |
||||||
|
this.setHandlers({ |
||||||
|
onMouseDown: () => { |
||||||
|
wasPlaying = !this.media.paused; |
||||||
|
wasPlaying && this.media.pause(); |
||||||
|
}, |
||||||
|
|
||||||
|
onMouseUp: (e) => { |
||||||
|
// cancelEvent(e.event);
|
||||||
|
wasPlaying && this.media.play(); |
||||||
|
} |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
protected onLoadedData = () => { |
||||||
|
this.max = this.media.duration; |
||||||
|
this.seek.setAttribute('max', '' + this.max); |
||||||
|
}; |
||||||
|
|
||||||
|
protected onEnded = () => { |
||||||
|
this.setProgress(); |
||||||
|
}; |
||||||
|
|
||||||
|
protected onPlay = () => { |
||||||
|
let r = () => { |
||||||
|
this.setProgress(); |
||||||
|
|
||||||
|
this.progressRAF = this.media.paused ? 0 : window.requestAnimationFrame(r); |
||||||
|
}; |
||||||
|
|
||||||
|
if(this.progressRAF) { |
||||||
|
window.cancelAnimationFrame(this.progressRAF); |
||||||
|
} |
||||||
|
|
||||||
|
if(this.streamable) { |
||||||
|
this.setLoadProgress(); |
||||||
|
} |
||||||
|
|
||||||
|
this.progressRAF = window.requestAnimationFrame(r); |
||||||
|
}; |
||||||
|
|
||||||
|
protected onTimeUpdate = () => { |
||||||
|
if(this.media.paused) { |
||||||
|
this.setProgress(); |
||||||
|
|
||||||
|
if(this.streamable) { |
||||||
|
this.setLoadProgress(); |
||||||
|
} |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
protected onProgress = (e: Event) => { |
||||||
|
this.setLoadProgress(); |
||||||
|
}; |
||||||
|
|
||||||
|
protected scrub(e: GrabEvent) { |
||||||
|
const scrubTime = super.scrub(e); |
||||||
|
this.media.currentTime = scrubTime; |
||||||
|
return scrubTime; |
||||||
|
} |
||||||
|
|
||||||
|
protected setLoadProgress() { |
||||||
|
if(appMediaPlaybackController.isSafariBuffering(this.media)) return; |
||||||
|
const buf = this.media.buffered; |
||||||
|
const numRanges = buf.length; |
||||||
|
|
||||||
|
const currentTime = this.media.currentTime; |
||||||
|
let nearestStart = 0, end = 0; |
||||||
|
for(let i = 0; i < numRanges; ++i) { |
||||||
|
const start = buf.start(i); |
||||||
|
if(currentTime >= start && start >= nearestStart) { |
||||||
|
nearestStart = start; |
||||||
|
end = buf.end(i); |
||||||
|
} |
||||||
|
|
||||||
|
//console.log('onProgress range:', i, buf.start(i), buf.end(i), this.media);
|
||||||
|
} |
||||||
|
|
||||||
|
//console.log('onProgress correct range:', nearestStart, end, this.media);
|
||||||
|
|
||||||
|
const percents = this.media.duration ? end / this.media.duration : 0; |
||||||
|
this.filledLoad.style.width = (percents * 100) + '%'; |
||||||
|
//this.filledLoad.style.transform = 'scaleX(' + percents + ')';
|
||||||
|
} |
||||||
|
|
||||||
|
protected setSeekMax() { |
||||||
|
this.max = this.media.duration || 0; |
||||||
|
if(this.max > 0) { |
||||||
|
this.onLoadedData(); |
||||||
|
} else { |
||||||
|
this.media.addEventListener('loadeddata', this.onLoadedData); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public setProgress() { |
||||||
|
if(appMediaPlaybackController.isSafariBuffering(this.media)) return; |
||||||
|
const currentTime = this.media.currentTime; |
||||||
|
|
||||||
|
super.setProgress(currentTime); |
||||||
|
} |
||||||
|
|
||||||
|
public setListeners() { |
||||||
|
super.setListeners(); |
||||||
|
this.media.addEventListener('ended', this.onEnded); |
||||||
|
this.media.addEventListener('play', this.onPlay); |
||||||
|
this.media.addEventListener('timeupdate', this.onTimeUpdate); |
||||||
|
this.streamable && this.media.addEventListener('progress', this.onProgress); |
||||||
|
} |
||||||
|
|
||||||
|
public removeListeners() { |
||||||
|
super.removeListeners(); |
||||||
|
|
||||||
|
if(this.media) { |
||||||
|
this.media.removeEventListener('loadeddata', this.onLoadedData); |
||||||
|
this.media.removeEventListener('ended', this.onEnded); |
||||||
|
this.media.removeEventListener('play', this.onPlay); |
||||||
|
this.media.removeEventListener('timeupdate', this.onTimeUpdate); |
||||||
|
this.streamable && this.media.removeEventListener('progress', this.onProgress); |
||||||
|
} |
||||||
|
|
||||||
|
if(this.progressRAF) { |
||||||
|
window.cancelAnimationFrame(this.progressRAF); |
||||||
|
this.progressRAF = 0; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,84 @@ |
|||||||
|
/* |
||||||
|
* https://github.com/morethanwords/tweb
|
||||||
|
* Copyright (C) 2019-2021 Eduard Kuzmenko |
||||||
|
* https://github.com/morethanwords/tweb/blob/master/LICENSE
|
||||||
|
*/ |
||||||
|
|
||||||
|
import cancelEvent from "../helpers/dom/cancelEvent"; |
||||||
|
import { attachClickEvent } from "../helpers/dom/clickEvent"; |
||||||
|
import ListenerSetter from "../helpers/listenerSetter"; |
||||||
|
import rootScope from "../lib/rootScope"; |
||||||
|
import appMediaPlaybackController from "./appMediaPlaybackController"; |
||||||
|
import RangeSelector from "./rangeSelector"; |
||||||
|
|
||||||
|
export default class VolumeSelector extends RangeSelector { |
||||||
|
private static ICONS = ['volume_off', 'volume_mute', 'volume_down', 'volume_up']; |
||||||
|
public btn: HTMLElement; |
||||||
|
protected icon: HTMLSpanElement; |
||||||
|
|
||||||
|
constructor(protected listenerSetter: ListenerSetter, protected vertical = false) { |
||||||
|
super({ |
||||||
|
step: 0.01, |
||||||
|
min: 0, |
||||||
|
max: 1, |
||||||
|
vertical |
||||||
|
}, 1); |
||||||
|
|
||||||
|
this.setListeners(); |
||||||
|
this.setHandlers({ |
||||||
|
onScrub: currentTime => { |
||||||
|
const value = Math.max(Math.min(currentTime, 1), 0); |
||||||
|
|
||||||
|
//console.log('volume scrub:', currentTime, value);
|
||||||
|
|
||||||
|
appMediaPlaybackController.muted = false; |
||||||
|
appMediaPlaybackController.volume = value; |
||||||
|
}, |
||||||
|
|
||||||
|
/* onMouseUp: (e) => { |
||||||
|
cancelEvent(e.event); |
||||||
|
} */ |
||||||
|
}); |
||||||
|
|
||||||
|
const className = 'player-volume'; |
||||||
|
const btn = this.btn = document.createElement('div'); |
||||||
|
btn.classList.add('btn-icon', className); |
||||||
|
const icon = this.icon = document.createElement('span'); |
||||||
|
icon.classList.add(className + '__icon'); |
||||||
|
|
||||||
|
btn.append(icon, this.container); |
||||||
|
|
||||||
|
attachClickEvent(icon, this.onMuteClick, {listenerSetter: this.listenerSetter}); |
||||||
|
this.listenerSetter.add(rootScope)('media_playback_params', this.setVolume); |
||||||
|
|
||||||
|
this.setVolume(); |
||||||
|
} |
||||||
|
|
||||||
|
private onMuteClick = (e?: Event) => { |
||||||
|
e && cancelEvent(e); |
||||||
|
appMediaPlaybackController.muted = !appMediaPlaybackController.muted; |
||||||
|
}; |
||||||
|
|
||||||
|
private setVolume = () => { |
||||||
|
// const volume = video.volume;
|
||||||
|
const {volume, muted} = appMediaPlaybackController; |
||||||
|
let d: string; |
||||||
|
let iconIndex: number; |
||||||
|
if(!volume || muted) { |
||||||
|
iconIndex = 0; |
||||||
|
} else if(volume > .5) { |
||||||
|
iconIndex = 3; |
||||||
|
} else if(volume > 0 && volume < .25) { |
||||||
|
iconIndex = 1; |
||||||
|
} else { |
||||||
|
iconIndex = 2; |
||||||
|
} |
||||||
|
|
||||||
|
VolumeSelector.ICONS.forEach(icon => this.icon.classList.remove('tgico-' + icon)); |
||||||
|
this.icon.classList.add('tgico-' + VolumeSelector.ICONS[iconIndex]); |
||||||
|
|
||||||
|
if(!this.mousedown) { |
||||||
|
this.setProgress(muted ? 0 : volume); |
||||||
|
} |
||||||
|
}; |
||||||
|
} |
@ -0,0 +1,8 @@ |
|||||||
|
export default function createVideo(options: { |
||||||
|
pip?: boolean |
||||||
|
} = {}) { |
||||||
|
const video = document.createElement('video'); |
||||||
|
if(!options.pip) video.disablePictureInPicture = true; |
||||||
|
video.setAttribute('playsinline', 'true'); |
||||||
|
return video; |
||||||
|
} |
Loading…
Reference in new issue