Picture-in-Picture
This commit is contained in:
parent
b48bc7610d
commit
28ea417d0a
@ -23,6 +23,7 @@ import SearchListLoader from "../helpers/searchListLoader";
|
|||||||
import { onMediaLoad } from "../helpers/files";
|
import { onMediaLoad } from "../helpers/files";
|
||||||
import copy from "../helpers/object/copy";
|
import copy from "../helpers/object/copy";
|
||||||
import deepEqual from "../helpers/object/deepEqual";
|
import deepEqual from "../helpers/object/deepEqual";
|
||||||
|
import ListenerSetter from "../helpers/listenerSetter";
|
||||||
|
|
||||||
// TODO: Safari: проверить стрим, включить его и сразу попробовать включить видео или другую песню
|
// TODO: Safari: проверить стрим, включить его и сразу попробовать включить видео или другую песню
|
||||||
// TODO: Safari: попробовать замаскировать подгрузку последнего чанка
|
// TODO: Safari: попробовать замаскировать подгрузку последнего чанка
|
||||||
@ -92,6 +93,8 @@ export class AppMediaPlaybackController {
|
|||||||
audio: 1
|
audio: 1
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private pip: HTMLVideoElement;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.container = document.createElement('div');
|
this.container = document.createElement('div');
|
||||||
//this.container.style.cssText = 'position: absolute; top: -10000px; left: -10000px;';
|
//this.container.style.cssText = 'position: absolute; top: -10000px; left: -10000px;';
|
||||||
@ -100,14 +103,14 @@ export class AppMediaPlaybackController {
|
|||||||
|
|
||||||
if(navigator.mediaSession) {
|
if(navigator.mediaSession) {
|
||||||
const actions: {[action in MediaSessionAction]?: MediaSessionActionHandler} = {
|
const actions: {[action in MediaSessionAction]?: MediaSessionActionHandler} = {
|
||||||
play: this.play,
|
play: this.browserPlay,
|
||||||
pause: this.pause,
|
pause: this.browserPause,
|
||||||
stop: this.stop,
|
stop: this.browserStop,
|
||||||
seekbackward: this.seekBackward,
|
seekbackward: this.browserSeekBackward,
|
||||||
seekforward: this.seekForward,
|
seekforward: this.browserSeekForward,
|
||||||
seekto: this.seekTo,
|
seekto: this.browserSeekTo,
|
||||||
previoustrack: this.previous,
|
previoustrack: this.browserPrevious,
|
||||||
nexttrack: this.next
|
nexttrack: this.browserNext
|
||||||
};
|
};
|
||||||
|
|
||||||
for(const action in actions) {
|
for(const action in actions) {
|
||||||
@ -178,22 +181,19 @@ export class AppMediaPlaybackController {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public seekBackward = (details: MediaSessionActionDetails) => {
|
public seekBackward = (details: MediaSessionActionDetails, media = this.playingMedia) => {
|
||||||
const media = this.playingMedia;
|
|
||||||
if(media) {
|
if(media) {
|
||||||
media.currentTime = Math.max(0, media.currentTime - (details.seekOffset || SEEK_OFFSET));
|
media.currentTime = Math.max(0, media.currentTime - (details.seekOffset || SEEK_OFFSET));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
public seekForward = (details: MediaSessionActionDetails) => {
|
public seekForward = (details: MediaSessionActionDetails, media = this.playingMedia) => {
|
||||||
const media = this.playingMedia;
|
|
||||||
if(media) {
|
if(media) {
|
||||||
media.currentTime = Math.min(media.duration, media.currentTime + (details.seekOffset || SEEK_OFFSET));
|
media.currentTime = Math.min(media.duration, media.currentTime + (details.seekOffset || SEEK_OFFSET));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
public seekTo = (details: MediaSessionActionDetails) => {
|
public seekTo = (details: MediaSessionActionDetails, media = this.playingMedia) => {
|
||||||
const media = this.playingMedia;
|
|
||||||
if(media) {
|
if(media) {
|
||||||
media.currentTime = details.seekTime;
|
media.currentTime = details.seekTime;
|
||||||
}
|
}
|
||||||
@ -393,6 +393,10 @@ export class AppMediaPlaybackController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async setNewMediadata(message: Message.message, playingMedia = this.playingMedia) {
|
private async setNewMediadata(message: Message.message, playingMedia = this.playingMedia) {
|
||||||
|
if(document.pictureInPictureElement) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
await onMediaLoad(playingMedia, undefined, false); // have to wait for load, otherwise on macOS won't set
|
await onMediaLoad(playingMedia, undefined, false); // have to wait for load, otherwise on macOS won't set
|
||||||
|
|
||||||
const doc = appMessagesManager.getMediaFromMessage(message) as MyDocument;
|
const doc = appMessagesManager.getMediaFromMessage(message) as MyDocument;
|
||||||
@ -493,6 +497,13 @@ export class AppMediaPlaybackController {
|
|||||||
navigator.mediaSession.metadata = metadata;
|
navigator.mediaSession.metadata = metadata;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public setCurrentMediadata() {
|
||||||
|
const {playingMedia} = this;
|
||||||
|
if(!playingMedia) return;
|
||||||
|
const message = this.getMessageByMedia(playingMedia);
|
||||||
|
this.setNewMediadata(message, playingMedia);
|
||||||
|
}
|
||||||
|
|
||||||
private getMessageByMedia(media: HTMLMediaElement): Message.message {
|
private getMessageByMedia(media: HTMLMediaElement): Message.message {
|
||||||
const details = this.mediaDetails.get(media);
|
const details = this.mediaDetails.get(media);
|
||||||
const {peerId, mid} = details;
|
const {peerId, mid} = details;
|
||||||
@ -500,6 +511,21 @@ export class AppMediaPlaybackController {
|
|||||||
return message;
|
return message;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public getPlayingDetails() {
|
||||||
|
const {playingMedia} = this;
|
||||||
|
if(!playingMedia) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const message = this.getMessageByMedia(playingMedia);
|
||||||
|
return {
|
||||||
|
doc: appMessagesManager.getMediaFromMessage(message),
|
||||||
|
message,
|
||||||
|
media: playingMedia,
|
||||||
|
playbackParams: this.getPlaybackParams()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
private onPlay = (e?: Event) => {
|
private onPlay = (e?: Event) => {
|
||||||
const media = e.target as HTMLMediaElement;
|
const media = e.target as HTMLMediaElement;
|
||||||
const details = this.mediaDetails.get(media);
|
const details = this.mediaDetails.get(media);
|
||||||
@ -507,6 +533,11 @@ export class AppMediaPlaybackController {
|
|||||||
|
|
||||||
//console.log('appMediaPlaybackController: video playing', this.currentPeerId, this.playingMedia, media);
|
//console.log('appMediaPlaybackController: video playing', this.currentPeerId, this.playingMedia, media);
|
||||||
|
|
||||||
|
const pip = this.pip;
|
||||||
|
if(pip) {
|
||||||
|
pip.pause();
|
||||||
|
}
|
||||||
|
|
||||||
const message = this.getMessageByMedia(media);
|
const message = this.getMessageByMedia(media);
|
||||||
|
|
||||||
const previousMedia = this.playingMedia;
|
const previousMedia = this.playingMedia;
|
||||||
@ -550,21 +581,6 @@ export class AppMediaPlaybackController {
|
|||||||
}, 0);
|
}, 0);
|
||||||
};
|
};
|
||||||
|
|
||||||
public getPlayingDetails() {
|
|
||||||
const {playingMedia} = this;
|
|
||||||
if(!playingMedia) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const message = this.getMessageByMedia(playingMedia);
|
|
||||||
return {
|
|
||||||
doc: appMessagesManager.getMediaFromMessage(message),
|
|
||||||
message,
|
|
||||||
media: playingMedia,
|
|
||||||
playbackParams: this.getPlaybackParams()
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private onPause = (e?: Event) => {
|
private onPause = (e?: Event) => {
|
||||||
/* const target = e.target as HTMLMediaElement;
|
/* const target = e.target as HTMLMediaElement;
|
||||||
if(!isInDOM(target)) {
|
if(!isInDOM(target)) {
|
||||||
@ -573,6 +589,10 @@ export class AppMediaPlaybackController {
|
|||||||
return;
|
return;
|
||||||
} */
|
} */
|
||||||
|
|
||||||
|
// if(this.pip) {
|
||||||
|
// this.pip.play();
|
||||||
|
// }
|
||||||
|
|
||||||
rootScope.dispatchEvent('media_pause');
|
rootScope.dispatchEvent('media_pause');
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -594,23 +614,27 @@ export class AppMediaPlaybackController {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
public toggle(play?: boolean) {
|
// public get pip() {
|
||||||
if(!this.playingMedia) {
|
// return document.pictureInPictureElement as HTMLVideoElement;
|
||||||
|
// }
|
||||||
|
|
||||||
|
public toggle(play?: boolean, media = this.playingMedia) {
|
||||||
|
if(!media) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(play === undefined) {
|
if(play === undefined) {
|
||||||
play = this.playingMedia.paused;
|
play = media.paused;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(this.playingMedia.paused !== play) {
|
if(media.paused !== play) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(play) {
|
if(play) {
|
||||||
this.playingMedia.play();
|
media.play();
|
||||||
} else {
|
} else {
|
||||||
this.playingMedia.pause();
|
media.pause();
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@ -624,8 +648,7 @@ export class AppMediaPlaybackController {
|
|||||||
return this.toggle(false);
|
return this.toggle(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
public stop = () => {
|
public stop = (media = this.playingMedia) => {
|
||||||
const media = this.playingMedia;
|
|
||||||
if(!media) {
|
if(!media) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -637,28 +660,30 @@ export class AppMediaPlaybackController {
|
|||||||
media.currentTime = 0;
|
media.currentTime = 0;
|
||||||
simulateEvent(media, 'ended');
|
simulateEvent(media, 'ended');
|
||||||
|
|
||||||
const details = this.mediaDetails.get(media);
|
if(media === this.playingMedia) {
|
||||||
if(details?.clean) {
|
const details = this.mediaDetails.get(media);
|
||||||
media.src = '';
|
if(details?.clean) {
|
||||||
const peerId = details.peerId;
|
media.src = '';
|
||||||
const s = details.isScheduled ? this.scheduled : this.media;
|
const peerId = details.peerId;
|
||||||
const storage = s.get(peerId);
|
const s = details.isScheduled ? this.scheduled : this.media;
|
||||||
if(storage) {
|
const storage = s.get(peerId);
|
||||||
storage.delete(details.mid);
|
if(storage) {
|
||||||
|
storage.delete(details.mid);
|
||||||
if(!storage.size) {
|
|
||||||
s.delete(peerId);
|
if(!storage.size) {
|
||||||
|
s.delete(peerId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
media.remove();
|
||||||
|
|
||||||
|
this.mediaDetails.delete(media);
|
||||||
}
|
}
|
||||||
|
|
||||||
media.remove();
|
this.playingMedia = undefined;
|
||||||
|
this.playingMediaType = undefined;
|
||||||
this.mediaDetails.delete(media);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.playingMedia = undefined;
|
|
||||||
this.playingMediaType = undefined;
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -690,22 +715,45 @@ export class AppMediaPlaybackController {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private bindBrowserCallback(cb: (video: HTMLVideoElement, details: MediaSessionActionDetails) => void) {
|
||||||
|
const handler: MediaSessionActionHandler = (details) => {
|
||||||
|
cb(this.pip, details);
|
||||||
|
};
|
||||||
|
|
||||||
|
return handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
public browserPlay = this.bindBrowserCallback((video) => this.toggle(true, video));
|
||||||
|
public browserPause = this.bindBrowserCallback((video) => this.toggle(false, video));
|
||||||
|
public browserStop = this.bindBrowserCallback((video) => this.stop(video));
|
||||||
|
public browserSeekBackward = this.bindBrowserCallback((video, details) => this.seekBackward(details, video));
|
||||||
|
public browserSeekForward = this.bindBrowserCallback((video, details) => this.seekForward(details, video));
|
||||||
|
public browserSeekTo = this.bindBrowserCallback((video, details) => this.seekTo(details, video));
|
||||||
|
public browserNext = this.bindBrowserCallback((video) => video || this.next());
|
||||||
|
public browserPrevious = this.bindBrowserCallback((video) => video ? this.seekToStart(video) : this.previous());
|
||||||
|
|
||||||
public next = () => {
|
public next = () => {
|
||||||
return this.go(1);
|
return this.go(1);
|
||||||
};
|
};
|
||||||
|
|
||||||
public previous = () => {
|
public previous = () => {
|
||||||
const media = this.playingMedia;
|
if(this.seekToStart(this.playingMedia)) {
|
||||||
// if(media && (media.currentTime > 5 || !this.listLoader.getPrevious().length)) {
|
|
||||||
if(media && media.currentTime > 5) {
|
|
||||||
media.currentTime = 0;
|
|
||||||
this.toggle(true);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.go(-1);
|
return this.go(-1);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
public seekToStart(media: HTMLMediaElement) {
|
||||||
|
if(media?.currentTime > 5) {
|
||||||
|
media.currentTime = 0;
|
||||||
|
this.toggle(true, media);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
public willBePlayed(media: HTMLMediaElement) {
|
public willBePlayed(media: HTMLMediaElement) {
|
||||||
this.willBePlayedMedia = media;
|
this.willBePlayedMedia = media;
|
||||||
}
|
}
|
||||||
@ -802,7 +850,7 @@ export class AppMediaPlaybackController {
|
|||||||
else this.playingMedia = undefined;
|
else this.playingMedia = undefined;
|
||||||
this.toggleSwitchers(false);
|
this.toggleSwitchers(false);
|
||||||
|
|
||||||
return () => {
|
return (playPaused = wasPlaying) => {
|
||||||
this.toggleSwitchers(true);
|
this.toggleSwitchers(true);
|
||||||
|
|
||||||
if(playingMedia) {
|
if(playingMedia) {
|
||||||
@ -817,7 +865,7 @@ export class AppMediaPlaybackController {
|
|||||||
this.stop();
|
this.stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
if(wasPlaying) {
|
if(playPaused) {
|
||||||
this.play();
|
this.play();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -826,6 +874,35 @@ export class AppMediaPlaybackController {
|
|||||||
public toggleSwitchers(enabled: boolean) {
|
public toggleSwitchers(enabled: boolean) {
|
||||||
this.lockedSwitchers = !enabled;
|
this.lockedSwitchers = !enabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public setPictureInPicture(video: HTMLVideoElement) {
|
||||||
|
this.pip = video;
|
||||||
|
|
||||||
|
// let wasPlaying = this.pause();
|
||||||
|
|
||||||
|
const listenerSetter = new ListenerSetter();
|
||||||
|
listenerSetter.add(video)('leavepictureinpicture', () => {
|
||||||
|
if(this.pip !== video) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.pip = undefined;
|
||||||
|
// if(wasPlaying) {
|
||||||
|
// this.play();
|
||||||
|
// }
|
||||||
|
|
||||||
|
listenerSetter.removeAll();
|
||||||
|
}, {once: true});
|
||||||
|
|
||||||
|
listenerSetter.add(video)('play', () => {
|
||||||
|
this.pause();
|
||||||
|
// if(this.pause()) {
|
||||||
|
// listenerSetter.add(video)('pause', () => {
|
||||||
|
// this.play();
|
||||||
|
// }, {once: true});
|
||||||
|
// }
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const appMediaPlaybackController = new AppMediaPlaybackController();
|
const appMediaPlaybackController = new AppMediaPlaybackController();
|
||||||
|
@ -14,7 +14,7 @@ import { logger } from "../lib/logger";
|
|||||||
import VideoPlayer from "../lib/mediaPlayer";
|
import VideoPlayer from "../lib/mediaPlayer";
|
||||||
import rootScope from "../lib/rootScope";
|
import rootScope from "../lib/rootScope";
|
||||||
import animationIntersector from "./animationIntersector";
|
import animationIntersector from "./animationIntersector";
|
||||||
import appMediaPlaybackController from "./appMediaPlaybackController";
|
import appMediaPlaybackController, { AppMediaPlaybackController } from "./appMediaPlaybackController";
|
||||||
import AvatarElement from "./avatar";
|
import AvatarElement from "./avatar";
|
||||||
import ButtonIcon from "./buttonIcon";
|
import ButtonIcon from "./buttonIcon";
|
||||||
import { ButtonMenuItemOptions } from "./buttonMenu";
|
import { ButtonMenuItemOptions } from "./buttonMenu";
|
||||||
@ -44,6 +44,8 @@ import RichTextProcessor from "../lib/richtextprocessor";
|
|||||||
import { NULL_PEER_ID } from "../lib/mtproto/mtproto_config";
|
import { NULL_PEER_ID } from "../lib/mtproto/mtproto_config";
|
||||||
import { isFullScreen } from "../helpers/dom/fullScreen";
|
import { isFullScreen } from "../helpers/dom/fullScreen";
|
||||||
import { attachClickEvent } from "../helpers/dom/clickEvent";
|
import { attachClickEvent } from "../helpers/dom/clickEvent";
|
||||||
|
import SearchListLoader from "../helpers/searchListLoader";
|
||||||
|
import createVideo from "../helpers/dom/createVideo";
|
||||||
|
|
||||||
const ZOOM_STEP = 0.5;
|
const ZOOM_STEP = 0.5;
|
||||||
const ZOOM_INITIAL_VALUE = 1;
|
const ZOOM_INITIAL_VALUE = 1;
|
||||||
@ -114,6 +116,7 @@ export default class AppMediaViewerBase<
|
|||||||
protected zoomSwipeY = 0;
|
protected zoomSwipeY = 0;
|
||||||
|
|
||||||
protected ctrlKeyDown: boolean;
|
protected ctrlKeyDown: boolean;
|
||||||
|
protected releaseSingleMedia: ReturnType<AppMediaPlaybackController['setSingleMedia']>;
|
||||||
|
|
||||||
get target() {
|
get target() {
|
||||||
return this.listLoader.current;
|
return this.listLoader.current;
|
||||||
@ -426,14 +429,11 @@ export default class AppMediaViewerBase<
|
|||||||
const promise = this.setMoverToTarget(this.target?.element, true).then(({onAnimationEnd}) => onAnimationEnd);
|
const promise = this.setMoverToTarget(this.target?.element, true).then(({onAnimationEnd}) => onAnimationEnd);
|
||||||
|
|
||||||
this.listLoader.reset();
|
this.listLoader.reset();
|
||||||
(this.listLoader as any).cleanup && (this.listLoader as any).cleanup();
|
(this.listLoader as SearchListLoader<any>).cleanup && (this.listLoader as SearchListLoader<any>).cleanup();
|
||||||
this.setMoverPromise = null;
|
this.setMoverPromise = null;
|
||||||
this.tempId = -1;
|
this.tempId = -1;
|
||||||
(window as any).appMediaViewer = undefined;
|
if((window as any).appMediaViewer === this) {
|
||||||
|
(window as any).appMediaViewer = undefined;
|
||||||
if(this.zoomSwipeHandler) {
|
|
||||||
this.zoomSwipeHandler.removeListeners();
|
|
||||||
this.zoomSwipeHandler = undefined;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* if(appSidebarRight.historyTabIDs.slice(-1)[0] === AppSidebarRight.SLIDERITEMSIDS.forward) {
|
/* if(appSidebarRight.historyTabIDs.slice(-1)[0] === AppSidebarRight.SLIDERITEMSIDS.forward) {
|
||||||
@ -442,19 +442,48 @@ export default class AppMediaViewerBase<
|
|||||||
});
|
});
|
||||||
} */
|
} */
|
||||||
|
|
||||||
window.removeEventListener('keydown', this.onKeyDown);
|
this.removeGlobalListeners();
|
||||||
window.removeEventListener('keyup', this.onKeyUp);
|
|
||||||
window.removeEventListener('wheel', this.onWheel, {capture: true});
|
this.zoomSwipeHandler = undefined;
|
||||||
|
|
||||||
promise.finally(() => {
|
promise.finally(() => {
|
||||||
this.wholeDiv.remove();
|
this.wholeDiv.remove();
|
||||||
rootScope.isOverlayActive = false;
|
this.toggleOverlay(false);
|
||||||
animationIntersector.checkAnimations(false);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return promise;
|
return promise;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected toggleOverlay(active: boolean) {
|
||||||
|
rootScope.isOverlayActive = active;
|
||||||
|
animationIntersector.checkAnimations(active);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected toggleGlobalListeners(active: boolean) {
|
||||||
|
if(active) this.setGlobalListeners();
|
||||||
|
else this.removeGlobalListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected removeGlobalListeners() {
|
||||||
|
if(this.zoomSwipeHandler) {
|
||||||
|
this.zoomSwipeHandler.removeListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
window.removeEventListener('keydown', this.onKeyDown);
|
||||||
|
window.removeEventListener('keyup', this.onKeyUp);
|
||||||
|
window.removeEventListener('wheel', this.onWheel, {capture: true});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected setGlobalListeners() {
|
||||||
|
if(this.isZooming()) {
|
||||||
|
this.zoomSwipeHandler.setListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener('keydown', this.onKeyDown);
|
||||||
|
window.addEventListener('keyup', this.onKeyUp);
|
||||||
|
if(!IS_TOUCH_SUPPORTED) window.addEventListener('wheel', this.onWheel, {passive: false, capture: true});
|
||||||
|
}
|
||||||
|
|
||||||
onClick = (e: MouseEvent) => {
|
onClick = (e: MouseEvent) => {
|
||||||
if(this.setMoverAnimationPromise) return;
|
if(this.setMoverAnimationPromise) return;
|
||||||
|
|
||||||
@ -770,7 +799,7 @@ export default class AppMediaViewerBase<
|
|||||||
mediaElement = new Image();
|
mediaElement = new Image();
|
||||||
src = target.src;
|
src = target.src;
|
||||||
} else if(target instanceof HTMLVideoElement) {
|
} else if(target instanceof HTMLVideoElement) {
|
||||||
mediaElement = document.createElement('video');
|
mediaElement = createVideo();
|
||||||
mediaElement.src = target.src;
|
mediaElement.src = target.src;
|
||||||
} else if(target instanceof SVGSVGElement) {
|
} else if(target instanceof SVGSVGElement) {
|
||||||
const clipId = target.dataset.clipId;
|
const clipId = target.dataset.clipId;
|
||||||
@ -878,10 +907,7 @@ export default class AppMediaViewerBase<
|
|||||||
mover.classList.add('hiding');
|
mover.classList.add('hiding');
|
||||||
}
|
}
|
||||||
|
|
||||||
this.wholeDiv.classList.add('backwards');
|
this.toggleWholeActive(false);
|
||||||
setTimeout(() => {
|
|
||||||
this.wholeDiv.classList.remove('active');
|
|
||||||
}, 0);
|
|
||||||
|
|
||||||
//return ret;
|
//return ret;
|
||||||
|
|
||||||
@ -969,6 +995,17 @@ export default class AppMediaViewerBase<
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected toggleWholeActive(active: boolean) {
|
||||||
|
if(active) {
|
||||||
|
this.wholeDiv.classList.add('active');
|
||||||
|
} else {
|
||||||
|
this.wholeDiv.classList.add('backwards');
|
||||||
|
setTimeout(() => {
|
||||||
|
this.wholeDiv.classList.remove('active');
|
||||||
|
}, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected setFullAspect(aspecter: HTMLDivElement, containerRect: DOMRect, rect: DOMRect) {
|
protected setFullAspect(aspecter: HTMLDivElement, containerRect: DOMRect, rect: DOMRect) {
|
||||||
/* let media = aspecter.firstElementChild;
|
/* let media = aspecter.firstElementChild;
|
||||||
let proportion: number;
|
let proportion: number;
|
||||||
@ -1209,15 +1246,15 @@ export default class AppMediaViewerBase<
|
|||||||
this.moveTheMover(this.content.mover, fromRight === 1);
|
this.moveTheMover(this.content.mover, fromRight === 1);
|
||||||
this.setNewMover();
|
this.setNewMover();
|
||||||
} else {
|
} else {
|
||||||
rootScope.isOverlayActive = true;
|
this.toggleOverlay(true);
|
||||||
window.addEventListener('keydown', this.onKeyDown);
|
this.setGlobalListeners();
|
||||||
window.addEventListener('keyup', this.onKeyUp);
|
|
||||||
if(!IS_TOUCH_SUPPORTED) window.addEventListener('wheel', this.onWheel, {passive: false, capture: true});
|
if(!this.wholeDiv.parentElement) {
|
||||||
const mainColumns = document.getElementById('main-columns');
|
this.pageEl.insertBefore(this.wholeDiv, document.getElementById('main-columns'));
|
||||||
this.pageEl.insertBefore(this.wholeDiv, mainColumns);
|
void this.wholeDiv.offsetLeft; // reflow
|
||||||
void this.wholeDiv.offsetLeft; // reflow
|
}
|
||||||
this.wholeDiv.classList.add('active');
|
|
||||||
animationIntersector.checkAnimations(true);
|
this.toggleWholeActive(true);
|
||||||
|
|
||||||
if(!IS_MOBILE_SAFARI) {
|
if(!IS_MOBILE_SAFARI) {
|
||||||
appNavigationController.pushItem({
|
appNavigationController.pushItem({
|
||||||
@ -1285,7 +1322,7 @@ export default class AppMediaViewerBase<
|
|||||||
const useController = message && media.type !== 'gif';
|
const useController = message && media.type !== 'gif';
|
||||||
const video = /* useController ?
|
const video = /* useController ?
|
||||||
appMediaPlaybackController.addMedia(message, false, true) as HTMLVideoElement :
|
appMediaPlaybackController.addMedia(message, false, true) as HTMLVideoElement :
|
||||||
*/document.createElement('video');
|
*/createVideo({pip: useController});
|
||||||
|
|
||||||
const set = () => this.setMoverToTarget(target, false, fromRight).then(({onAnimationEnd}) => {
|
const set = () => this.setMoverToTarget(target, false, fromRight).then(({onAnimationEnd}) => {
|
||||||
//return; // set and don't move
|
//return; // set and don't move
|
||||||
@ -1366,6 +1403,32 @@ export default class AppMediaViewerBase<
|
|||||||
streamable: supportsStreaming,
|
streamable: supportsStreaming,
|
||||||
onPlaybackRackMenuToggle: (open) => {
|
onPlaybackRackMenuToggle: (open) => {
|
||||||
this.wholeDiv.classList.toggle('hide-caption', !!open);
|
this.wholeDiv.classList.toggle('hide-caption', !!open);
|
||||||
|
},
|
||||||
|
onPip: (pip) => {
|
||||||
|
if(!pip && (window as any).appMediaViewer !== this) {
|
||||||
|
this.releaseSingleMedia = undefined;
|
||||||
|
this.close();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const mover = this.moversContainer.lastElementChild as HTMLElement;
|
||||||
|
mover.classList.toggle('hiding', pip);
|
||||||
|
this.toggleWholeActive(!pip);
|
||||||
|
this.toggleOverlay(!pip);
|
||||||
|
this.toggleGlobalListeners(!pip);
|
||||||
|
|
||||||
|
if(useController) {
|
||||||
|
if(pip) {
|
||||||
|
// appMediaPlaybackController.toggleSwitchers(true);
|
||||||
|
|
||||||
|
this.releaseSingleMedia(false);
|
||||||
|
this.releaseSingleMedia = undefined;
|
||||||
|
|
||||||
|
appMediaPlaybackController.setPictureInPicture(video);
|
||||||
|
} else {
|
||||||
|
this.releaseSingleMedia = appMediaPlaybackController.setSingleMedia(video, message as Message.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
player.addEventListener('toggleControls', (show) => {
|
player.addEventListener('toggleControls', (show) => {
|
||||||
@ -1374,7 +1437,7 @@ export default class AppMediaViewerBase<
|
|||||||
|
|
||||||
this.addEventListener('setMoverBefore', () => {
|
this.addEventListener('setMoverBefore', () => {
|
||||||
this.wholeDiv.classList.remove('has-video-controls');
|
this.wholeDiv.classList.remove('has-video-controls');
|
||||||
this.videoPlayer.removeListeners();
|
this.videoPlayer.cleanup();
|
||||||
this.videoPlayer = undefined;
|
this.videoPlayer = undefined;
|
||||||
}, {once: true});
|
}, {once: true});
|
||||||
|
|
||||||
@ -1465,10 +1528,13 @@ export default class AppMediaViewerBase<
|
|||||||
// * have to set options (especially playbackRate) after src
|
// * have to set options (especially playbackRate) after src
|
||||||
// * https://github.com/videojs/video.js/issues/2516
|
// * https://github.com/videojs/video.js/issues/2516
|
||||||
if(useController) {
|
if(useController) {
|
||||||
const rollback = appMediaPlaybackController.setSingleMedia(video, message as Message.message);
|
this.releaseSingleMedia = appMediaPlaybackController.setSingleMedia(video, message as Message.message);
|
||||||
|
|
||||||
this.addEventListener('setMoverBefore', () => {
|
this.addEventListener('setMoverBefore', () => {
|
||||||
rollback();
|
if(this.releaseSingleMedia) {
|
||||||
|
this.releaseSingleMedia();
|
||||||
|
this.releaseSingleMedia = undefined;
|
||||||
|
}
|
||||||
}, {once: true});
|
}, {once: true});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,7 +7,6 @@
|
|||||||
import appDocsManager, {MyDocument} from "../lib/appManagers/appDocsManager";
|
import appDocsManager, {MyDocument} from "../lib/appManagers/appDocsManager";
|
||||||
import { wrapPhoto } from "./wrappers";
|
import { wrapPhoto } from "./wrappers";
|
||||||
import ProgressivePreloader from "./preloader";
|
import ProgressivePreloader from "./preloader";
|
||||||
import { MediaProgressLine } from "../lib/mediaPlayer";
|
|
||||||
import appMediaPlaybackController, { MediaItem, MediaSearchContext } from "./appMediaPlaybackController";
|
import appMediaPlaybackController, { MediaItem, MediaSearchContext } from "./appMediaPlaybackController";
|
||||||
import { DocumentAttribute, Message } from "../layer";
|
import { DocumentAttribute, Message } from "../layer";
|
||||||
import mediaSizes from "../helpers/mediaSizes";
|
import mediaSizes from "../helpers/mediaSizes";
|
||||||
@ -32,6 +31,7 @@ import formatBytes from "../helpers/formatBytes";
|
|||||||
import { animateSingle } from "../helpers/animation";
|
import { animateSingle } from "../helpers/animation";
|
||||||
import clamp from "../helpers/number/clamp";
|
import clamp from "../helpers/number/clamp";
|
||||||
import toHHMMSS from "../helpers/string/toHHMMSS";
|
import toHHMMSS from "../helpers/string/toHHMMSS";
|
||||||
|
import MediaProgressLine from "./mediaProgressLine";
|
||||||
|
|
||||||
rootScope.addEventListener('messages_media_read', ({mids, peerId}) => {
|
rootScope.addEventListener('messages_media_read', ({mids, peerId}) => {
|
||||||
mids.forEach(mid => {
|
mids.forEach(mid => {
|
||||||
|
@ -17,10 +17,11 @@ import replaceContent from "../../helpers/dom/replaceContent";
|
|||||||
import PeerTitle from "../peerTitle";
|
import PeerTitle from "../peerTitle";
|
||||||
import { i18n } from "../../lib/langPack";
|
import { i18n } from "../../lib/langPack";
|
||||||
import { formatFullSentTime } from "../../helpers/date";
|
import { formatFullSentTime } from "../../helpers/date";
|
||||||
import { MediaProgressLine, VolumeSelector } from "../../lib/mediaPlayer";
|
|
||||||
import ButtonIcon from "../buttonIcon";
|
import ButtonIcon from "../buttonIcon";
|
||||||
import { MyDocument } from "../../lib/appManagers/appDocsManager";
|
import { MyDocument } from "../../lib/appManagers/appDocsManager";
|
||||||
import { Message } from "../../layer";
|
import { Message } from "../../layer";
|
||||||
|
import MediaProgressLine from "../mediaProgressLine";
|
||||||
|
import VolumeSelector from "../volumeSelector";
|
||||||
|
|
||||||
export default class ChatAudio extends PinnedContainer {
|
export default class ChatAudio extends PinnedContainer {
|
||||||
private toggleEl: HTMLElement;
|
private toggleEl: HTMLElement;
|
||||||
|
@ -3390,6 +3390,9 @@ export default class ChatBubbles {
|
|||||||
preview.classList.add('preview');
|
preview.classList.add('preview');
|
||||||
previewResizer.append(preview);
|
previewResizer.append(preview);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let quoteTextDiv = document.createElement('div');
|
||||||
|
quoteTextDiv.classList.add('quote-text');
|
||||||
|
|
||||||
const doc = webpage.document as MyDocument;
|
const doc = webpage.document as MyDocument;
|
||||||
if(doc) {
|
if(doc) {
|
||||||
@ -3422,18 +3425,23 @@ export default class ChatBubbles {
|
|||||||
autoDownloadSize: this.chat.autoDownload.file,
|
autoDownloadSize: this.chat.autoDownload.file,
|
||||||
lazyLoadQueue: this.lazyLoadQueue,
|
lazyLoadQueue: this.lazyLoadQueue,
|
||||||
loadPromises,
|
loadPromises,
|
||||||
sizeType: 'documentName'
|
sizeType: 'documentName',
|
||||||
|
searchContext: {
|
||||||
|
useSearch: false,
|
||||||
|
peerId: this.peerId,
|
||||||
|
inputFilter: {
|
||||||
|
_: 'inputMessagesFilterEmpty'
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
preview.append(docDiv);
|
preview.append(docDiv);
|
||||||
preview.classList.add('preview-with-document');
|
preview.classList.add('preview-with-document');
|
||||||
|
quoteTextDiv.classList.add('has-document');
|
||||||
//messageDiv.classList.add((webpage.type || 'document') + '-message');
|
//messageDiv.classList.add((webpage.type || 'document') + '-message');
|
||||||
//doc = null;
|
//doc = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let quoteTextDiv = document.createElement('div');
|
|
||||||
quoteTextDiv.classList.add('quote-text');
|
|
||||||
|
|
||||||
if(previewResizer) {
|
if(previewResizer) {
|
||||||
quoteTextDiv.append(previewResizer);
|
quoteTextDiv.append(previewResizer);
|
||||||
}
|
}
|
||||||
|
180
src/components/mediaProgressLine.ts
Normal file
180
src/components/mediaProgressLine.ts
Normal file
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -25,6 +25,7 @@ import { attachClickEvent } from "../../helpers/dom/clickEvent";
|
|||||||
import MEDIA_MIME_TYPES_SUPPORTED from '../../environment/mediaMimeTypesSupport';
|
import MEDIA_MIME_TYPES_SUPPORTED from '../../environment/mediaMimeTypesSupport';
|
||||||
import getGifDuration from "../../helpers/getGifDuration";
|
import getGifDuration from "../../helpers/getGifDuration";
|
||||||
import replaceContent from "../../helpers/dom/replaceContent";
|
import replaceContent from "../../helpers/dom/replaceContent";
|
||||||
|
import createVideo from "../../helpers/dom/createVideo";
|
||||||
|
|
||||||
type SendFileParams = Partial<{
|
type SendFileParams = Partial<{
|
||||||
file: File,
|
file: File,
|
||||||
@ -275,13 +276,12 @@ export default class PopupNewMedia extends PopupElement {
|
|||||||
|
|
||||||
let promise: Promise<void>;
|
let promise: Promise<void>;
|
||||||
if(isVideo) {
|
if(isVideo) {
|
||||||
const video = document.createElement('video');
|
const video = createVideo();
|
||||||
const source = document.createElement('source');
|
const source = document.createElement('source');
|
||||||
source.src = params.objectURL = URL.createObjectURL(file);
|
source.src = params.objectURL = URL.createObjectURL(file);
|
||||||
video.autoplay = true;
|
video.autoplay = true;
|
||||||
video.controls = false;
|
video.controls = false;
|
||||||
video.muted = true;
|
video.muted = true;
|
||||||
video.setAttribute('playsinline', 'true');
|
|
||||||
|
|
||||||
video.addEventListener('timeupdate', () => {
|
video.addEventListener('timeupdate', () => {
|
||||||
video.pause();
|
video.pause();
|
||||||
|
84
src/components/volumeSelector.ts
Normal file
84
src/components/volumeSelector.ts
Normal file
@ -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);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
@ -58,6 +58,7 @@ import Row from './row';
|
|||||||
import { ChatAutoDownloadSettings } from '../helpers/autoDownload';
|
import { ChatAutoDownloadSettings } from '../helpers/autoDownload';
|
||||||
import formatBytes from '../helpers/formatBytes';
|
import formatBytes from '../helpers/formatBytes';
|
||||||
import toHHMMSS from '../helpers/string/toHHMMSS';
|
import toHHMMSS from '../helpers/string/toHHMMSS';
|
||||||
|
import createVideo from '../helpers/dom/createVideo';
|
||||||
|
|
||||||
const MAX_VIDEO_AUTOPLAY_SIZE = 50 * 1024 * 1024; // 50 MB
|
const MAX_VIDEO_AUTOPLAY_SIZE = 50 * 1024 * 1024; // 50 MB
|
||||||
|
|
||||||
@ -183,9 +184,8 @@ export function wrapVideo({doc, container, message, boxWidth, boxHeight, withTai
|
|||||||
|
|
||||||
let preloader: ProgressivePreloader; // it must be here, otherwise will get error before initialization in round onPlay
|
let preloader: ProgressivePreloader; // it must be here, otherwise will get error before initialization in round onPlay
|
||||||
|
|
||||||
const video = document.createElement('video');
|
const video = createVideo();
|
||||||
video.classList.add('media-video');
|
video.classList.add('media-video');
|
||||||
video.setAttribute('playsinline', 'true');
|
|
||||||
video.muted = true;
|
video.muted = true;
|
||||||
if(doc.type === 'round') {
|
if(doc.type === 'round') {
|
||||||
const divRound = document.createElement('div');
|
const divRound = document.createElement('div');
|
||||||
@ -1650,8 +1650,7 @@ export function wrapSticker({doc, div, middleware, lazyLoadQueue, group, play, o
|
|||||||
if(asStatic) {
|
if(asStatic) {
|
||||||
media = new Image();
|
media = new Image();
|
||||||
} else {
|
} else {
|
||||||
media = document.createElement('video');
|
media = createVideo();
|
||||||
media.setAttribute('playsinline', 'true');
|
|
||||||
(media as HTMLVideoElement).muted = true;
|
(media as HTMLVideoElement).muted = true;
|
||||||
|
|
||||||
if(play) {
|
if(play) {
|
||||||
@ -1783,8 +1782,7 @@ export async function wrapStickerSetThumb({set, lazyLoadQueue, container, group,
|
|||||||
} else {
|
} else {
|
||||||
let media: HTMLElement;
|
let media: HTMLElement;
|
||||||
if(set.pFlags.videos) {
|
if(set.pFlags.videos) {
|
||||||
media = document.createElement('video');
|
media = createVideo();
|
||||||
media.setAttribute('playsinline', 'true');
|
|
||||||
(media as HTMLVideoElement).autoplay = true;
|
(media as HTMLVideoElement).autoplay = true;
|
||||||
(media as HTMLVideoElement).muted = true;
|
(media as HTMLVideoElement).muted = true;
|
||||||
(media as HTMLVideoElement).loop = true;
|
(media as HTMLVideoElement).loop = true;
|
||||||
|
8
src/helpers/dom/createVideo.ts
Normal file
8
src/helpers/dom/createVideo.ts
Normal file
@ -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;
|
||||||
|
}
|
@ -173,9 +173,10 @@ export default class SearchListLoader<Item extends {mid: number, peerId: PeerId}
|
|||||||
this.loadedAllUp = true;
|
this.loadedAllUp = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// if(!this.searchContext.useSearch) {
|
// it should've been noSearch instead...
|
||||||
// this.loadedAllDown = this.loadedAllUp = true;
|
if(this.searchContext.useSearch !== false) {
|
||||||
// }
|
this.loadedAllDown = this.loadedAllUp = true;
|
||||||
|
}
|
||||||
|
|
||||||
if(this.otherSideLoader) {
|
if(this.otherSideLoader) {
|
||||||
this.otherSideLoader.setSearchContext(context);
|
this.otherSideLoader.setSearchContext(context);
|
||||||
@ -270,7 +271,11 @@ export default class SearchListLoader<Item extends {mid: number, peerId: PeerId}
|
|||||||
protected setLoaded(down: boolean, value: boolean) {
|
protected setLoaded(down: boolean, value: boolean) {
|
||||||
const changed = super.setLoaded(down, value);
|
const changed = super.setLoaded(down, value);
|
||||||
|
|
||||||
if(changed && this.otherSideLoader && value/* && (this.reverse ? this.loadedAllUp : this.loadedAllDown) */) {
|
if(changed &&
|
||||||
|
this.otherSideLoader &&
|
||||||
|
value &&
|
||||||
|
this.searchContext?.useSearch !== false/* &&
|
||||||
|
(this.reverse ? this.loadedAllUp : this.loadedAllDown) */) {
|
||||||
const reverse = this.loadedAllUp;
|
const reverse = this.loadedAllUp;
|
||||||
this.otherSideLoader.setSearchContext({
|
this.otherSideLoader.setSearchContext({
|
||||||
...this.searchContext,
|
...this.searchContext,
|
||||||
|
@ -5,263 +5,19 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import appMediaPlaybackController from "../components/appMediaPlaybackController";
|
import appMediaPlaybackController from "../components/appMediaPlaybackController";
|
||||||
import { IS_APPLE_MOBILE } from "../environment/userAgent";
|
import { IS_APPLE_MOBILE, IS_MOBILE } from "../environment/userAgent";
|
||||||
import { IS_TOUCH_SUPPORTED } from "../environment/touchSupport";
|
import { IS_TOUCH_SUPPORTED } from "../environment/touchSupport";
|
||||||
import RangeSelector from "../components/rangeSelector";
|
|
||||||
import { onMediaLoad } from "../helpers/files";
|
import { onMediaLoad } from "../helpers/files";
|
||||||
import cancelEvent from "../helpers/dom/cancelEvent";
|
import cancelEvent from "../helpers/dom/cancelEvent";
|
||||||
import ListenerSetter from "../helpers/listenerSetter";
|
import ListenerSetter from "../helpers/listenerSetter";
|
||||||
import ButtonMenu from "../components/buttonMenu";
|
import ButtonMenu from "../components/buttonMenu";
|
||||||
import { ButtonMenuToggleHandler } from "../components/buttonMenuToggle";
|
import { ButtonMenuToggleHandler } from "../components/buttonMenuToggle";
|
||||||
import rootScope from "./rootScope";
|
import rootScope from "./rootScope";
|
||||||
import { GrabEvent } from "../helpers/dom/attachGrabListeners";
|
|
||||||
import { attachClickEvent } from "../helpers/dom/clickEvent";
|
|
||||||
import ControlsHover from "../helpers/dom/controlsHover";
|
import ControlsHover from "../helpers/dom/controlsHover";
|
||||||
import { addFullScreenListener, cancelFullScreen, isFullScreen, requestFullScreen } from "../helpers/dom/fullScreen";
|
import { addFullScreenListener, cancelFullScreen, isFullScreen, requestFullScreen } from "../helpers/dom/fullScreen";
|
||||||
import toHHMMSS from "../helpers/string/toHHMMSS";
|
import toHHMMSS from "../helpers/string/toHHMMSS";
|
||||||
|
import MediaProgressLine from "../components/mediaProgressLine";
|
||||||
export class MediaProgressLine extends RangeSelector {
|
import VolumeSelector from "../components/volumeSelector";
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export 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);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export default class VideoPlayer extends ControlsHover {
|
export default class VideoPlayer extends ControlsHover {
|
||||||
private static PLAYBACK_RATES = [0.5, 1, 1.5, 2];
|
private static PLAYBACK_RATES = [0.5, 1, 1.5, 2];
|
||||||
@ -274,18 +30,21 @@ export default class VideoPlayer extends ControlsHover {
|
|||||||
|
|
||||||
protected listenerSetter: ListenerSetter;
|
protected listenerSetter: ListenerSetter;
|
||||||
protected playbackRateButton: HTMLElement;
|
protected playbackRateButton: HTMLElement;
|
||||||
|
protected pipButton: HTMLElement;
|
||||||
|
|
||||||
/* protected videoParent: HTMLElement;
|
/* protected videoParent: HTMLElement;
|
||||||
protected videoWhichChild: number; */
|
protected videoWhichChild: number; */
|
||||||
|
|
||||||
protected onPlaybackRackMenuToggle?: (open: boolean) => void;
|
protected onPlaybackRackMenuToggle?: (open: boolean) => void;
|
||||||
|
protected onPip?: (pip: boolean) => void;
|
||||||
|
|
||||||
constructor({video, play = false, streamable = false, duration, onPlaybackRackMenuToggle}: {
|
constructor({video, play = false, streamable = false, duration, onPlaybackRackMenuToggle, onPip}: {
|
||||||
video: HTMLVideoElement,
|
video: HTMLVideoElement,
|
||||||
play?: boolean,
|
play?: boolean,
|
||||||
streamable?: boolean,
|
streamable?: boolean,
|
||||||
duration?: number,
|
duration?: number,
|
||||||
onPlaybackRackMenuToggle?: (open: boolean) => void
|
onPlaybackRackMenuToggle?: VideoPlayer['onPlaybackRackMenuToggle'],
|
||||||
|
onPip: VideoPlayer['onPip']
|
||||||
}) {
|
}) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
@ -294,6 +53,7 @@ export default class VideoPlayer extends ControlsHover {
|
|||||||
this.wrapper.classList.add('ckin__player');
|
this.wrapper.classList.add('ckin__player');
|
||||||
|
|
||||||
this.onPlaybackRackMenuToggle = onPlaybackRackMenuToggle;
|
this.onPlaybackRackMenuToggle = onPlaybackRackMenuToggle;
|
||||||
|
this.onPip = onPip;
|
||||||
|
|
||||||
this.listenerSetter = new ListenerSetter();
|
this.listenerSetter = new ListenerSetter();
|
||||||
|
|
||||||
@ -347,6 +107,7 @@ export default class VideoPlayer extends ControlsHover {
|
|||||||
|
|
||||||
if(skin === 'default') {
|
if(skin === 'default') {
|
||||||
this.playbackRateButton = this.wrapper.querySelector('.playback-rate') as HTMLElement;
|
this.playbackRateButton = this.wrapper.querySelector('.playback-rate') as HTMLElement;
|
||||||
|
this.pipButton = this.wrapper.querySelector('.pip') as HTMLElement;
|
||||||
|
|
||||||
const toggle = wrapper.querySelectorAll('.toggle') as NodeListOf<HTMLElement>;
|
const toggle = wrapper.querySelectorAll('.toggle') as NodeListOf<HTMLElement>;
|
||||||
const fullScreenButton = wrapper.querySelector('.fullscreen') as HTMLElement;
|
const fullScreenButton = wrapper.querySelector('.fullscreen') as HTMLElement;
|
||||||
@ -366,6 +127,25 @@ export default class VideoPlayer extends ControlsHover {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if(this.pipButton) {
|
||||||
|
listenerSetter.add(this.pipButton)('click', () => {
|
||||||
|
this.video.requestPictureInPicture();
|
||||||
|
});
|
||||||
|
|
||||||
|
const onPip = (pip: boolean) => {
|
||||||
|
this.wrapper.style.visibility = pip ? 'hidden': '';
|
||||||
|
this.onPip(pip);
|
||||||
|
};
|
||||||
|
|
||||||
|
listenerSetter.add(video)('enterpictureinpicture', () => {
|
||||||
|
onPip(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
listenerSetter.add(video)('leavepictureinpicture', () => {
|
||||||
|
onPip(false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if(!IS_TOUCH_SUPPORTED) {
|
if(!IS_TOUCH_SUPPORTED) {
|
||||||
listenerSetter.add(video)('click', () => {
|
listenerSetter.add(video)('click', () => {
|
||||||
this.togglePlay();
|
this.togglePlay();
|
||||||
@ -406,17 +186,6 @@ export default class VideoPlayer extends ControlsHover {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/* player.addEventListener('click', (e) => {
|
|
||||||
if(e.target !== player) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.togglePlay();
|
|
||||||
}); */
|
|
||||||
|
|
||||||
/* video.addEventListener('play', () => {
|
|
||||||
}); */
|
|
||||||
|
|
||||||
listenerSetter.add(video)('dblclick', () => {
|
listenerSetter.add(video)('dblclick', () => {
|
||||||
if(!IS_TOUCH_SUPPORTED) {
|
if(!IS_TOUCH_SUPPORTED) {
|
||||||
@ -492,6 +261,7 @@ export default class VideoPlayer extends ControlsHover {
|
|||||||
</div>
|
</div>
|
||||||
<div class="right-controls">
|
<div class="right-controls">
|
||||||
<button class="btn-icon ${skin}__button btn-menu-toggle playback-rate night" title="Playback Rate"></button>
|
<button class="btn-icon ${skin}__button btn-menu-toggle playback-rate night" title="Playback Rate"></button>
|
||||||
|
${!IS_MOBILE && document.pictureInPictureEnabled ? `<button class="btn-icon ${skin}__button pip tgico-pip" title="Picture-in-Picture"></button>` : ''}
|
||||||
<button class="btn-icon ${skin}__button fullscreen tgico-fullscreen" title="Full Screen"></button>
|
<button class="btn-icon ${skin}__button fullscreen tgico-fullscreen" title="Full Screen"></button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -593,10 +363,10 @@ export default class VideoPlayer extends ControlsHover {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public removeListeners() {
|
public cleanup() {
|
||||||
super.cleanup();
|
super.cleanup();
|
||||||
this.listenerSetter.removeAll();
|
this.listenerSetter.removeAll();
|
||||||
this.progress.removeListeners();
|
this.progress.removeListeners();
|
||||||
this.onPlaybackRackMenuToggle = undefined;
|
this.onPlaybackRackMenuToggle = this.onPip = undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -338,7 +338,7 @@ $background-transition-total-time: #{$input-transition-time - $background-transi
|
|||||||
font-size: 2rem;
|
font-size: 2rem;
|
||||||
|
|
||||||
&:before {
|
&:before {
|
||||||
font-weight: bold;
|
font-weight: var(--font-weight-bold);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -543,6 +543,7 @@ $bubble-beside-button-width: 38px;
|
|||||||
&.webpage {
|
&.webpage {
|
||||||
.preview-with-document {
|
.preview-with-document {
|
||||||
margin-bottom: 0 !important;
|
margin-bottom: 0 !important;
|
||||||
|
min-width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.document {
|
.document {
|
||||||
@ -552,6 +553,15 @@ $bubble-beside-button-width: 38px;
|
|||||||
padding-left: 44px;
|
padding-left: 44px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.has-document {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
.preview-resizer {
|
||||||
|
order: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.preview-resizer {
|
.preview-resizer {
|
||||||
|
@ -87,7 +87,6 @@
|
|||||||
padding: 0;
|
padding: 0;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
visibility: visible;
|
|
||||||
top: 50%;
|
top: 50%;
|
||||||
left: 50%;
|
left: 50%;
|
||||||
transform: translate3d(-50%, -50%, 0) scale(1);
|
transform: translate3d(-50%, -50%, 0) scale(1);
|
||||||
@ -98,7 +97,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
@include animation-level(2) {
|
@include animation-level(2) {
|
||||||
transition: visibility var(--layer-transition), opacity var(--layer-transition);
|
transition: opacity var(--layer-transition);
|
||||||
}
|
}
|
||||||
|
|
||||||
@include respond-to(handhelds) {
|
@include respond-to(handhelds) {
|
||||||
@ -110,7 +109,6 @@
|
|||||||
&:not(.played) {
|
&:not(.played) {
|
||||||
.default__button--big {
|
.default__button--big {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
visibility: hidden;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -205,7 +203,6 @@
|
|||||||
&.is-playing, &:not(.played) {
|
&.is-playing, &:not(.played) {
|
||||||
.default__button--big {
|
.default__button--big {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
visibility: hidden;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.toggle:not(.default__button--big) {
|
.toggle:not(.default__button--big) {
|
||||||
|
@ -122,7 +122,7 @@ poll-element {
|
|||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
height: 16px;
|
height: 16px;
|
||||||
width: 16px;
|
width: 16px;
|
||||||
font-weight: bold;
|
font-weight: var(--font-weight-bold);
|
||||||
font-size: .75rem;
|
font-size: .75rem;
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -132,7 +132,7 @@ poll-element {
|
|||||||
&:before {
|
&:before {
|
||||||
content: $tgico-check;
|
content: $tgico-check;
|
||||||
//margin-left: 1px;
|
//margin-left: 1px;
|
||||||
font-weight: bold;
|
font-weight: var(--font-weight-bold);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -192,7 +192,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&-day {
|
&-day {
|
||||||
font-weight: bold;
|
font-weight: var(--font-weight-bold);
|
||||||
color: var(--primary-text-color) !important;
|
color: var(--primary-text-color) !important;
|
||||||
font-size: 14px !important;
|
font-size: 14px !important;
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.chat-title {
|
.chat-title {
|
||||||
font-weight: bold;
|
font-weight: var(--font-weight-bold);
|
||||||
margin: .75rem 0 .25rem;
|
margin: .75rem 0 .25rem;
|
||||||
line-height: var(--line-height);
|
line-height: var(--line-height);
|
||||||
}
|
}
|
||||||
|
@ -21,3 +21,10 @@ $tgico-font-path: "assets/fonts" !default;
|
|||||||
|
|
||||||
@import "tgico/style";
|
@import "tgico/style";
|
||||||
@import "tgico/variables";
|
@import "tgico/variables";
|
||||||
|
|
||||||
|
.tgico-phone_filled {
|
||||||
|
&:before {
|
||||||
|
content: $tgico-endcall_filled;
|
||||||
|
transform: rotate(-135deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user