Some minor W2G and general changes
parent
516bcefc36
commit
6b623a4d11
|
@ -1,6 +1,9 @@
|
|||
# Changelog:
|
||||
* **09.08.20**
|
||||
- Added a "watch to gather" context menu entry for clients
|
||||
|
||||
* **08.08.20**
|
||||
- Added a watch to gether mode
|
||||
- Added a watch to gather mode
|
||||
- Added API support for the popout able browsers for the native client
|
||||
|
||||
* **05.08.20**
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" height="100%" version="1.1" viewBox="0 0 68 48" width="100%">
|
||||
<path d="M66.52,7.74c-0.78-2.93-2.49-5.41-5.42-6.19C55.79,.13,34,0,34,0S12.21,.13,6.9,1.55 C3.97,2.33,2.27,4.81,1.48,7.74C0.06,13.05,0,24,0,24s0.06,10.95,1.48,16.26c0.78,2.93,2.49,5.41,5.42,6.19 C12.21,47.87,34,48,34,48s21.79-0.13,27.1-1.55c2.93-0.78,4.64-3.26,5.42-6.19C67.94,34.95,68,24,68,24S67.94,13.05,66.52,7.74z" fill="#f00"></path>
|
||||
<path d="M 45,24 27,14 27,34" fill="#fff"></path>
|
||||
</svg>
|
After Width: | Height: | Size: 506 B |
|
@ -36,7 +36,7 @@ import {guid} from "tc-shared/crypto/uid";
|
|||
import {ServerEventLog} from "tc-shared/ui/frames/log/ServerEventLog";
|
||||
import {EventType} from "tc-shared/ui/frames/log/Definitions";
|
||||
import {PluginCmdRegistry} from "tc-shared/connection/PluginCmdHandler";
|
||||
import {W2GPluginCmdHandler} from "tc-shared/video-viewer/W2GPluginHandler";
|
||||
import {W2GPluginCmdHandler} from "tc-shared/video-viewer/W2GPlugin";
|
||||
|
||||
export enum DisconnectReason {
|
||||
HANDLER_DESTROYED,
|
||||
|
|
|
@ -16,6 +16,14 @@ export interface ClientGlobalControlEvents {
|
|||
connection?: ConnectionHandler
|
||||
},
|
||||
|
||||
action_w2g: {
|
||||
following: number,
|
||||
handlerId: string
|
||||
} | {
|
||||
videoUrl: string,
|
||||
handlerId: string
|
||||
}
|
||||
|
||||
/* some more specific window openings */
|
||||
action_open_window_connect: {
|
||||
new_tab: boolean
|
||||
|
|
|
@ -41,7 +41,7 @@ import "./ui/elements/ContextDivider";
|
|||
import "./ui/elements/Tab";
|
||||
import "./connection/CommandHandler";
|
||||
import {ConnectRequestData} from "tc-shared/ipc/ConnectHandler";
|
||||
import {openVideoViewer} from "tc-shared/video-viewer/Controller";
|
||||
import "./video-viewer/Controller";
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
|
@ -497,8 +497,6 @@ function main() {
|
|||
modal.close_listener.push(() => settings.changeGlobal(Settings.KEY_USER_IS_NEW, false));
|
||||
}
|
||||
|
||||
(window as any).spawnVideoPopout = openVideoViewer;
|
||||
|
||||
//spawnVideoPopout(server_connections.active_connection(), "https://www.youtube.com/watch?v=9683D18fyvs");
|
||||
}
|
||||
|
||||
|
|
|
@ -8,8 +8,8 @@ import {HTMLRenderer} from "tc-shared/ui/react-elements/HTMLRenderer";
|
|||
import * as contextmenu from "tc-shared/ui/elements/ContextMenu";
|
||||
import {spawn_context_menu} from "tc-shared/ui/elements/ContextMenu";
|
||||
import {copy_to_clipboard} from "tc-shared/utils/helpers";
|
||||
import {openVideoViewer} from "tc-shared/video-viewer/Controller";
|
||||
import {server_connections} from "tc-shared/ui/frames/connection_handlers";
|
||||
import {global_client_actions} from "tc-shared/events/GlobalEvents";
|
||||
|
||||
const playIcon = require("./yt-play-button.svg");
|
||||
const cssStyle = require("./youtube.scss");
|
||||
|
@ -36,7 +36,10 @@ loader.register_task(loader.Stage.JAVASCRIPT_INITIALIZING, {
|
|||
|
||||
spawn_context_menu(event.pageX, event.pageY, {
|
||||
callback: () => {
|
||||
openVideoViewer(server_connections.active_connection(), text);
|
||||
global_client_actions.fire("action_w2g", {
|
||||
videoUrl: text,
|
||||
handlerId: server_connections.active_connection().handlerId
|
||||
});
|
||||
},
|
||||
name: tr("Watch video"),
|
||||
type: contextmenu.MenuEntryType.ENTRY,
|
||||
|
@ -59,7 +62,10 @@ loader.register_task(loader.Stage.JAVASCRIPT_INITIALIZING, {
|
|||
>
|
||||
<img draggable={false} src={"https://img.youtube.com/vi/" + result[1] + "/hqdefault.jpg"} alt={"Video thumbnail"} title={tra("Youtube video {}", result[1])} />
|
||||
<button className={cssStyle.playButton} onClick={() => {
|
||||
openVideoViewer(server_connections.active_connection(), text);
|
||||
global_client_actions.fire("action_w2g", {
|
||||
videoUrl: text,
|
||||
handlerId: server_connections.active_connection().handlerId
|
||||
});
|
||||
}}>
|
||||
<HTMLRenderer purify={false}>{playIcon}</HTMLRenderer>
|
||||
</button>
|
||||
|
|
|
@ -27,6 +27,8 @@ import {ChannelTreeEntry, ChannelTreeEntryEvents} from "tc-shared/ui/TreeEntry";
|
|||
import {spawnClientVolumeChange, spawnMusicBotVolumeChange} from "tc-shared/ui/modal/ModalChangeVolumeNew";
|
||||
import {spawnPermissionEditorModal} from "tc-shared/ui/modal/permission/ModalPermissionEditor";
|
||||
import {EventClient, EventType} from "tc-shared/ui/frames/log/Definitions";
|
||||
import {W2GPluginCmdHandler} from "tc-shared/video-viewer/W2GPlugin";
|
||||
import {global_client_actions} from "tc-shared/events/GlobalEvents";
|
||||
|
||||
export enum ClientType {
|
||||
CLIENT_VOICE,
|
||||
|
@ -508,6 +510,8 @@ export class ClientEntry extends ChannelTreeEntry<ClientEvents> {
|
|||
}
|
||||
|
||||
showContextMenu(x: number, y: number, on_close: () => void = undefined) {
|
||||
const w2gPlugin = this.channelTree.client.getPluginCmdRegistry().getPluginHandler<W2GPluginCmdHandler>(W2GPluginCmdHandler.kPluginChannel);
|
||||
|
||||
let trigger_close = true;
|
||||
contextmenu.spawn_context_menu(x, y,
|
||||
...this.contextmenu_info(), {
|
||||
|
@ -519,6 +523,17 @@ export class ClientEntry extends ChannelTreeEntry<ClientEvents> {
|
|||
callback: () => {
|
||||
this.open_text_chat();
|
||||
}
|
||||
}, {
|
||||
type: contextmenu.MenuEntryType.ENTRY,
|
||||
name: tr("Watch clients video"),
|
||||
icon_path: "img/icon_w2g.svg",
|
||||
visible: w2gPlugin?.getCurrentWatchers().findIndex(e => e.clientId === this.clientId()) !== -1,
|
||||
callback: () => {
|
||||
global_client_actions.fire("action_w2g", {
|
||||
following: this.clientId(),
|
||||
handlerId: this.channelTree.client.handlerId
|
||||
});
|
||||
}
|
||||
},
|
||||
contextmenu.Entry.HR(),
|
||||
{
|
||||
|
|
|
@ -2,6 +2,7 @@ import {ClientEntry} from "tc-shared/ui/client";
|
|||
import {ConnectionHandler, ConnectionState} from "tc-shared/ConnectionHandler";
|
||||
import {EventHandler, Registry} from "tc-shared/events";
|
||||
import {
|
||||
PrivateConversationManagerEvents,
|
||||
PrivateConversationInfo,
|
||||
PrivateConversationUIEvents
|
||||
} from "tc-shared/ui/frames/side/PrivateConversationDefinitions";
|
||||
|
@ -252,6 +253,9 @@ export class PrivateConversation extends AbstractChat<PrivateConversationUIEvent
|
|||
/* TODO: Move this somehow to the client itself? */
|
||||
if(this.activeClient instanceof ClientEntry)
|
||||
this.activeClient.setUnread(timestamp !== undefined);
|
||||
|
||||
/* TODO: Eliminate this cross reference? */
|
||||
this.connection.side_bar.info_frame().update_chat_counter();
|
||||
}
|
||||
|
||||
protected canClientAccessChat(): boolean {
|
||||
|
@ -311,6 +315,7 @@ export class PrivateConversation extends AbstractChat<PrivateConversationUIEvent
|
|||
}
|
||||
|
||||
export class PrivateConversationManager extends AbstractChatManager<PrivateConversationUIEvents> {
|
||||
public readonly events: Registry<PrivateConversationManagerEvents>;
|
||||
public readonly htmlTag: HTMLDivElement;
|
||||
public readonly connection: ConnectionHandler;
|
||||
|
||||
|
@ -322,6 +327,7 @@ export class PrivateConversationManager extends AbstractChatManager<PrivateConve
|
|||
constructor(connection: ConnectionHandler) {
|
||||
super();
|
||||
this.connection = connection;
|
||||
this.events = new Registry<PrivateConversationManagerEvents>();
|
||||
|
||||
this.htmlTag = document.createElement("div");
|
||||
this.htmlTag.style.display = "flex";
|
||||
|
|
|
@ -10,6 +10,8 @@ export type ModalType = "error" | "warning" | "info" | "none";
|
|||
|
||||
export interface ModalOptions {
|
||||
destroyOnClose?: boolean;
|
||||
|
||||
defaultSize?: { width: number, height: number };
|
||||
}
|
||||
|
||||
export interface ModalEvents {
|
||||
|
|
|
@ -4,9 +4,12 @@ import {spawnExternalModal} from "tc-shared/ui/react-elements/external-modal";
|
|||
import {EventHandler, Registry} from "tc-shared/events";
|
||||
import {VideoViewerEvents} from "./Definitions";
|
||||
import {ConnectionHandler} from "tc-shared/ConnectionHandler";
|
||||
import {W2GPluginCmdHandler, W2GWatcher, W2GWatcherFollower} from "tc-shared/video-viewer/W2GPluginHandler";
|
||||
import {W2GPluginCmdHandler, W2GWatcher, W2GWatcherFollower} from "tc-shared/video-viewer/W2GPlugin";
|
||||
import {ModalController} from "tc-shared/ui/react-elements/Modal";
|
||||
import {settings, Settings} from "tc-shared/settings";
|
||||
import {global_client_actions} from "tc-shared/events/GlobalEvents";
|
||||
import {server_connections} from "tc-shared/ui/frames/connection_handlers";
|
||||
import {createErrorModal} from "tc-shared/ui/elements/Modal";
|
||||
|
||||
const parseWatcherId = (id: string): { clientId: number, clientUniqueId: string} => {
|
||||
const [ clientIdString, clientUniqueId ] = id.split(" - ");
|
||||
|
@ -26,7 +29,7 @@ class VideoViewer {
|
|||
private unregisterCallbacks = [];
|
||||
private destroyCalled = false;
|
||||
|
||||
constructor(connection: ConnectionHandler, initialUrl: string) {
|
||||
constructor(connection: ConnectionHandler) {
|
||||
this.connection = connection;
|
||||
|
||||
this.events = new Registry<VideoViewerEvents>();
|
||||
|
@ -37,8 +40,7 @@ class VideoViewer {
|
|||
throw tr("Missing video viewer plugin");
|
||||
}
|
||||
|
||||
this.modal = spawnExternalModal("video-viewer", this.events, { handlerId: connection.handlerId, url: initialUrl });
|
||||
this.setWatchingVideo(initialUrl);
|
||||
this.modal = spawnExternalModal("video-viewer", this.events, { handlerId: connection.handlerId });
|
||||
|
||||
this.registerPluginListeners();
|
||||
this.plugin.getCurrentWatchers().forEach(watcher => this.registerWatcherEvents(watcher));
|
||||
|
@ -64,8 +66,16 @@ class VideoViewer {
|
|||
if(this.currentVideoUrl === url)
|
||||
return;
|
||||
|
||||
this.events.fire_async("notify_following", { watcherId: undefined });
|
||||
this.events.fire_async("notify_video", { url: url });
|
||||
this.plugin.setLocalWatcherStatus(url, { status: "paused" });
|
||||
this.events.fire_async("notify_video", { url: url }); /* notify the new url */
|
||||
}
|
||||
|
||||
setFollowing(target: W2GWatcher) {
|
||||
if(this.plugin.getLocalFollowingWatcher() === target)
|
||||
return;
|
||||
|
||||
this.plugin.setLocalFollowing(target, { status: "paused" });
|
||||
this.events.fire_async("notify_video", { url: target.getCurrentVideo() }); /* notify the new url */
|
||||
}
|
||||
|
||||
async open() {
|
||||
|
@ -291,45 +301,57 @@ class VideoViewer {
|
|||
settings.changeGlobal(Settings.KEY_W2G_SIDEBAR_COLLAPSED, !event.shown);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@EventHandler<VideoViewerEvents>("notify_video")
|
||||
private handleVideo(event: VideoViewerEvents["notify_video"]) {
|
||||
if(this.currentVideoUrl === event.url)
|
||||
return;
|
||||
|
||||
this.currentVideoUrl = event.url;
|
||||
const following = this.plugin.getLocalFollowingWatcher();
|
||||
if(following)
|
||||
this.plugin.setLocalFollowing(following, { status: "paused" });
|
||||
else
|
||||
this.plugin.setLocalWatcherStatus(this.currentVideoUrl, { status: "paused" });
|
||||
this.notifyWatcherList();
|
||||
}
|
||||
}
|
||||
|
||||
let currentVideoViewer: VideoViewer;
|
||||
|
||||
export function openVideoViewer(connection: ConnectionHandler, url: string) {
|
||||
if(currentVideoViewer?.connection === connection) {
|
||||
currentVideoViewer.setWatchingVideo(url);
|
||||
currentVideoViewer.open(); /* draw focus */
|
||||
return;
|
||||
} else if(currentVideoViewer) {
|
||||
global_client_actions.on("action_w2g", event => {
|
||||
const connection = server_connections.findConnection(event.handlerId);
|
||||
if(connection === undefined) return;
|
||||
|
||||
const plugin = connection.getPluginCmdRegistry().getPluginHandler<W2GPluginCmdHandler>(W2GPluginCmdHandler.kPluginChannel);
|
||||
|
||||
let watcher: W2GWatcher;
|
||||
if('following' in event) {
|
||||
watcher = plugin.getCurrentWatchers().find(e => e.clientId === event.following);
|
||||
if(!watcher) {
|
||||
createErrorModal(tr("Target client isn't watching anything"), tr("The target client isn't watching anything.")).open();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if(currentVideoViewer && currentVideoViewer.connection !== connection) {
|
||||
currentVideoViewer.destroy();
|
||||
currentVideoViewer = undefined;
|
||||
}
|
||||
|
||||
currentVideoViewer = new VideoViewer(connection, url);
|
||||
currentVideoViewer.events.on("notify_destroy", () => {
|
||||
currentVideoViewer = undefined;
|
||||
});
|
||||
if(!currentVideoViewer) {
|
||||
currentVideoViewer = new VideoViewer(connection);
|
||||
currentVideoViewer.events.on("notify_destroy", () => {
|
||||
currentVideoViewer = undefined;
|
||||
});
|
||||
}
|
||||
|
||||
if('following' in event) {
|
||||
currentVideoViewer.setFollowing(watcher);
|
||||
} else {
|
||||
currentVideoViewer.setWatchingVideo(event.videoUrl);
|
||||
}
|
||||
|
||||
currentVideoViewer.open().catch(error => {
|
||||
logError(LogCategory.GENERAL, tr("Failed to open video viewer: %o"), error);
|
||||
currentVideoViewer.destroy();
|
||||
currentVideoViewer = undefined;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
window.onbeforeunload = () => {
|
||||
currentVideoViewer?.destroy();
|
||||
|
|
|
@ -127,7 +127,7 @@ const WatcherInfo = React.memo((props: { events: Registry<VideoViewerEvents>, wa
|
|||
if(Math.abs(expectedPlaytime - currentPlaytime) > 2) {
|
||||
setStatus(Object.assign({ timestamp: Date.now() }, event.status));
|
||||
} else {
|
||||
/* keep the last value, its still close enought */
|
||||
/* keep the last value, its still close enough */
|
||||
setStatus({
|
||||
status: "playing",
|
||||
timestamp: status.timestamp,
|
||||
|
@ -305,6 +305,8 @@ const PlayerController = React.memo((props: { events: Registry<VideoViewerEvents
|
|||
const [ masterPlayerState, setWatcherPlayerState ] = useState<"playing" | "buffering" | "paused" | "stopped">("stopped");
|
||||
const watcherTimestamp = useRef<number>();
|
||||
|
||||
const videoEnded = useRef(false);
|
||||
|
||||
const [ forcePause, setForcePause ] = useState(false);
|
||||
|
||||
props.events.reactUse("notify_following", event => setMode(event.watcherId === undefined ? "watcher" : "follower"));
|
||||
|
@ -375,10 +377,19 @@ const PlayerController = React.memo((props: { events: Registry<VideoViewerEvents
|
|||
kLogPlayerEvents && log.trace(LogCategory.GENERAL, tr("ReactPlayer::onEnded()"));
|
||||
playerState.current = "stopped";
|
||||
props.events.fire("notify_local_status", { status: { status: "stopped" } });
|
||||
|
||||
videoEnded.current = true;
|
||||
player.current.seekTo(0, "seconds");
|
||||
}}
|
||||
|
||||
onPause={() => {
|
||||
kLogPlayerEvents && log.trace(LogCategory.GENERAL, tr("ReactPlayer::onPause()"));
|
||||
|
||||
if(videoEnded.current) {
|
||||
videoEnded.current = false;
|
||||
return;
|
||||
}
|
||||
|
||||
playerState.current = "paused";
|
||||
props.events.fire("notify_local_status", { status: { status: "paused" } });
|
||||
}}
|
||||
|
@ -386,6 +397,11 @@ const PlayerController = React.memo((props: { events: Registry<VideoViewerEvents
|
|||
onPlay={() => {
|
||||
kLogPlayerEvents && log.trace(LogCategory.GENERAL, tr("ReactPlayer::onPlay()"));
|
||||
|
||||
if(videoEnded.current) {
|
||||
/* it's just the seek to the beginning */
|
||||
return;
|
||||
}
|
||||
|
||||
if(mode === "follower") {
|
||||
if(masterPlayerState !== "playing") {
|
||||
setForcePause(true);
|
||||
|
@ -399,6 +415,7 @@ const PlayerController = React.memo((props: { events: Registry<VideoViewerEvents
|
|||
log.debug(LogCategory.GENERAL, tr("Player started, at second %d. Watcher is at %s. So sync: %o"), currentSeconds, expectedSeconds, doSync);
|
||||
doSync && player.current.seekTo(expectedSeconds, "seconds");
|
||||
}
|
||||
|
||||
playerState.current = "playing";
|
||||
props.events.fire("notify_local_status", { status: { status: "playing", timestampBuffer: currentTime.current.buffer, timestampPlay: currentTime.current.play } });
|
||||
}}
|
||||
|
@ -436,6 +453,13 @@ const PlayerController = React.memo((props: { events: Registry<VideoViewerEvents
|
|||
loop={false}
|
||||
light={false}
|
||||
|
||||
config={{
|
||||
youtube: {
|
||||
playerVars: {
|
||||
rel: 0
|
||||
}
|
||||
}
|
||||
}}
|
||||
playing={mode === "watcher" ? undefined : masterPlayerState === "playing" || forcePause}
|
||||
/>
|
||||
);
|
||||
|
|
|
@ -195,6 +195,8 @@ export class W2GPluginCmdHandler extends PluginCmdHandler {
|
|||
static readonly kStatusUpdateTimeout = 10000;
|
||||
|
||||
readonly events: Registry<W2GEvents>;
|
||||
private readonly callbackWatcherEvents;
|
||||
|
||||
private currentWatchers: InternalW2GWatcher[] = [];
|
||||
|
||||
private localPlayerStatus: PlayerStatus;
|
||||
|
@ -202,8 +204,6 @@ export class W2GPluginCmdHandler extends PluginCmdHandler {
|
|||
private localFollowing: InternalW2GWatcher | undefined;
|
||||
private localStatusUpdateTimer: number;
|
||||
|
||||
private callbackWatcherEvents;
|
||||
|
||||
constructor() {
|
||||
super(W2GPluginCmdHandler.kPluginChannel);
|
||||
this.events = new Registry<W2GEvents>();
|
|
@ -89,15 +89,15 @@ class ExternalModalController extends AbstractExternalModalController {
|
|||
"loader-abort": __build.mode === "debug" ? 1 : 0,
|
||||
};
|
||||
|
||||
const options = this.getOptions();
|
||||
const features = {
|
||||
status: "no",
|
||||
location: "no",
|
||||
toolbar: "no",
|
||||
menubar: "no",
|
||||
/*
|
||||
width: 600,
|
||||
height: 400
|
||||
*/
|
||||
resizable: "yes",
|
||||
width: options.defaultSize?.width,
|
||||
height: options.defaultSize?.height
|
||||
};
|
||||
|
||||
let baseUrl = location.origin + location.pathname + "?";
|
||||
|
|
Loading…
Reference in New Issue