Some minor W2G and general changes

canary
WolverinDEV 2020-08-08 15:20:32 +02:00
parent 516bcefc36
commit 6b623a4d11
13 changed files with 127 additions and 39 deletions

View File

@ -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**

4
shared/img/icon_w2g.svg Normal file
View File

@ -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

View File

@ -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,

View File

@ -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

View File

@ -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");
}

View File

@ -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>

View File

@ -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(),
{

View File

@ -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";

View File

@ -10,6 +10,8 @@ export type ModalType = "error" | "warning" | "info" | "none";
export interface ModalOptions {
destroyOnClose?: boolean;
defaultSize?: { width: number, height: number };
}
export interface ModalEvents {

View File

@ -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();

View File

@ -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}
/>
);

View File

@ -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>();

View File

@ -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 + "?";