Merge pull request #23 from billsonnn/feature/youtube-tv

Feature/youtube tv
This commit is contained in:
Bill 2021-11-12 23:41:25 -05:00 committed by GitHub
commit 29fcaa31b4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 428 additions and 0 deletions

View File

@ -22,6 +22,7 @@
"react-slider": "^1.3.1", "react-slider": "^1.3.1",
"react-transition-group": "^4.4.2", "react-transition-group": "^4.4.2",
"react-virtualized": "^9.22.3", "react-virtualized": "^9.22.3",
"react-youtube": "^7.13.1",
"typescript": "^4.3.5", "typescript": "^4.3.5",
"web-vitals": "^1.1.2" "web-vitals": "^1.1.2"
}, },

View File

@ -0,0 +1,27 @@
import { RoomWidgetUpdateEvent } from './RoomWidgetUpdateEvent';
export class RoomWidgetUpdateYoutubeDisplayEvent extends RoomWidgetUpdateEvent
{
public static UPDATE_YOUTUBE_DISPLAY: string = 'RWUEIE_UPDATE_YOUTUBE_DISPLAY';
private _objectId: number;
private _hasControl: boolean;
constructor(objectId: number, hasControl = false)
{
super(RoomWidgetUpdateYoutubeDisplayEvent.UPDATE_YOUTUBE_DISPLAY);
this._objectId = objectId;
this._hasControl = hasControl;
}
public get objectId(): number
{
return this._objectId;
}
public get hasControl(): boolean
{
return this._hasControl;
}
}

View File

@ -0,0 +1,74 @@
import { SecurityLevel } from '@nitrots/nitro-renderer';
import { NitroEvent } from '@nitrots/nitro-renderer/src/core/events/NitroEvent';
import { GetYoutubeDisplayStatusMessageComposer } from '@nitrots/nitro-renderer/src/nitro/communication/messages/outgoing/room/furniture/youtube';
import { RoomEngineTriggerWidgetEvent } from '@nitrots/nitro-renderer/src/nitro/room/events/RoomEngineTriggerWidgetEvent';
import { RoomWidgetEnum } from '@nitrots/nitro-renderer/src/nitro/ui/widget/enums/RoomWidgetEnum';
import { RoomWidgetMessage, RoomWidgetUpdateEvent } from '..';
import { GetSessionDataManager, IsOwnerOfFurniture } from '../../..';
import { SendMessageHook } from '../../../../../hooks';
import { GetRoomEngine } from '../../GetRoomEngine';
import { RoomWidgetUpdateYoutubeDisplayEvent } from '../events/RoomWidgetUpdateYoutubeDisplayEvent';
import { RoomWidgetHandler } from './RoomWidgetHandler';
export class FurnitureYoutubeDisplayWidgetHandler extends RoomWidgetHandler
{
public static readonly CONTROL_COMMAND_PREVIOUS_VIDEO = 0;
public static readonly CONTROL_COMMAND_NEXT_VIDEO = 1;
public static readonly CONTROL_COMMAND_PAUSE_VIDEO = 2;
public static readonly CONTROL_COMMAND_CONTINUE_VIDEO = 3;
private _lastFurniId: number = -1;
public processEvent(event: NitroEvent): void
{
switch(event.type)
{
case RoomEngineTriggerWidgetEvent.OPEN_WIDGET: {
const widgetEvent = (event as RoomEngineTriggerWidgetEvent);
const roomObject = GetRoomEngine().getRoomObject(widgetEvent.roomId, widgetEvent.objectId, widgetEvent.category);
if(!roomObject) return;
this._lastFurniId = widgetEvent.objectId;
const hasControl = GetSessionDataManager().hasSecurity(SecurityLevel.EMPLOYEE) || IsOwnerOfFurniture(roomObject);
this.container.eventDispatcher.dispatchEvent(new RoomWidgetUpdateYoutubeDisplayEvent(roomObject.id, hasControl));
SendMessageHook(new GetYoutubeDisplayStatusMessageComposer(this._lastFurniId));
return;
}
case RoomEngineTriggerWidgetEvent.CLOSE_WIDGET: {
const widgetEvent = (event as RoomEngineTriggerWidgetEvent);
if(widgetEvent.objectId !== this._lastFurniId) return;
this.container.eventDispatcher.dispatchEvent(new RoomWidgetUpdateYoutubeDisplayEvent(-1));
return;
}
}
}
public processWidgetMessage(message: RoomWidgetMessage): RoomWidgetUpdateEvent
{
switch(message.type)
{
}
return null;
}
public get type(): string
{
return RoomWidgetEnum.YOUTUBE;
}
public get eventTypes(): string[]
{
return [];
}
public get messageTypes(): string[]
{
return [];
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 164 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 249 B

View File

@ -693,6 +693,17 @@
height: 16px; height: 16px;
} }
&.icon-youtube-next {
background: url("../images/room-widgets/youtube-widget/next.png");
width: 21px;
height: 16px;
}
&.icon-youtube-prev {
background: url("../images/room-widgets/youtube-widget/prev.png");
width: 21px;
height: 16px;
}
&.icon-friendlist-warning { &.icon-friendlist-warning {
background: url("../images/friendlist/icons/icon_warning.png"); background: url("../images/friendlist/icons/icon_warning.png");
width: 23px; width: 23px;

View File

@ -1,6 +1,7 @@
import { EventDispatcher, NitroRectangle, RoomGeometry, RoomVariableEnum, Vector3d } from '@nitrots/nitro-renderer'; import { EventDispatcher, NitroRectangle, RoomGeometry, RoomVariableEnum, Vector3d } from '@nitrots/nitro-renderer';
import { FC, useEffect, useRef, useState } from 'react'; import { FC, useEffect, useRef, useState } from 'react';
import { DispatchMouseEvent, DispatchTouchEvent, DoorbellWidgetHandler, FurniChooserWidgetHandler, FurnitureContextMenuWidgetHandler, FurnitureCreditWidgetHandler, FurnitureCustomStackHeightWidgetHandler, FurnitureDimmerWidgetHandler, FurnitureExternalImageWidgetHandler, FurnitureMannequinWidgetHandler, FurniturePresentWidgetHandler, GetNitroInstance, GetRoomEngine, InitializeRoomInstanceRenderingCanvas, IRoomWidgetHandlerManager, RoomWidgetAvatarInfoHandler, RoomWidgetChatHandler, RoomWidgetChatInputHandler, RoomWidgetHandlerManager, RoomWidgetInfostandHandler, RoomWidgetRoomToolsHandler, RoomWidgetUpdateRoomViewEvent, UserChooserWidgetHandler } from '../../api'; import { DispatchMouseEvent, DispatchTouchEvent, DoorbellWidgetHandler, FurniChooserWidgetHandler, FurnitureContextMenuWidgetHandler, FurnitureCreditWidgetHandler, FurnitureCustomStackHeightWidgetHandler, FurnitureDimmerWidgetHandler, FurnitureExternalImageWidgetHandler, FurnitureMannequinWidgetHandler, FurniturePresentWidgetHandler, GetNitroInstance, GetRoomEngine, InitializeRoomInstanceRenderingCanvas, IRoomWidgetHandlerManager, RoomWidgetAvatarInfoHandler, RoomWidgetChatHandler, RoomWidgetChatInputHandler, RoomWidgetHandlerManager, RoomWidgetInfostandHandler, RoomWidgetRoomToolsHandler, RoomWidgetUpdateRoomViewEvent, UserChooserWidgetHandler } from '../../api';
import { FurnitureYoutubeDisplayWidgetHandler } from '../../api/nitro/room/widgets/handlers/FurnitureYoutubeDisplayWidgetHandler';
import { RoomContextProvider } from './context/RoomContext'; import { RoomContextProvider } from './context/RoomContext';
import { RoomColorView } from './RoomColorView'; import { RoomColorView } from './RoomColorView';
import { RoomViewProps } from './RoomView.types'; import { RoomViewProps } from './RoomView.types';
@ -44,6 +45,7 @@ export const RoomView: FC<RoomViewProps> = props =>
widgetHandlerManager.registerHandler(new FurnitureExternalImageWidgetHandler()); widgetHandlerManager.registerHandler(new FurnitureExternalImageWidgetHandler());
widgetHandlerManager.registerHandler(new FurniturePresentWidgetHandler()); widgetHandlerManager.registerHandler(new FurniturePresentWidgetHandler());
widgetHandlerManager.registerHandler(new FurnitureDimmerWidgetHandler()); widgetHandlerManager.registerHandler(new FurnitureDimmerWidgetHandler());
widgetHandlerManager.registerHandler(new FurnitureYoutubeDisplayWidgetHandler());
widgetHandlerManager.registerHandler(new FurnitureMannequinWidgetHandler()); widgetHandlerManager.registerHandler(new FurnitureMannequinWidgetHandler());
setWidgetHandler(widgetHandlerManager); setWidgetHandler(widgetHandlerManager);

View File

@ -13,3 +13,4 @@
@import './stickie/FurnitureStickieView'; @import './stickie/FurnitureStickieView';
@import './high-score/FurnitureHighScoreView'; @import './high-score/FurnitureHighScoreView';
@import './gift-opening/FurnitureGiftOpeningView'; @import './gift-opening/FurnitureGiftOpeningView';
@import './youtube-tv/FurnitureYoutubeDisplayView';

View File

@ -13,6 +13,7 @@ import { FurnitureManipulationMenuView } from './manipulation-menu/FurnitureMani
import { FurnitureMannequinView } from './mannequin/FurnitureMannequinView'; import { FurnitureMannequinView } from './mannequin/FurnitureMannequinView';
import { FurnitureStickieView } from './stickie/FurnitureStickieView'; import { FurnitureStickieView } from './stickie/FurnitureStickieView';
import { FurnitureTrophyView } from './trophy/FurnitureTrophyView'; import { FurnitureTrophyView } from './trophy/FurnitureTrophyView';
import { FurnitureYoutubeDisplayView } from './youtube-tv/FurnitureYoutubeDisplayView';
export const FurnitureWidgetsView: FC<{}> = props => export const FurnitureWidgetsView: FC<{}> = props =>
{ {
@ -32,6 +33,7 @@ export const FurnitureWidgetsView: FC<{}> = props =>
<FurnitureTrophyView /> <FurnitureTrophyView />
<FurnitureBadgeDisplayView /> <FurnitureBadgeDisplayView />
<FurnitureExternalImageView /> <FurnitureExternalImageView />
<FurnitureYoutubeDisplayView />
</div> </div>
); );
} }

View File

@ -0,0 +1,54 @@
.youtube-tv-widget {
width: 600px;
height: 380px;
.youtube-video-container {
//min-height: 366px;
.empty-video {
background-color: black;
color: white;
width: 100%;
height: 100%;
text-align: center;
}
.youtubeContainer {
position: relative;
width: 100%;
height: 100%;
//height: 0;
//padding-bottom: 56.25%;
overflow: hidden;
margin-bottom: 50px;
}
.youtubeContainer iframe {
width: 100%;
height: 100%;
position: absolute;
top: 0;
left: 0;
}
}
.playlist-container {
overflow-y: hidden;
margin-right: -10px;
color: black;
height: 100%;
.playlist-controls {
width: 100%;
.icon {
margin-right: 10px;
margin-bottom: 10px;
}
}
.playlist-grid {
height: 100%;
width: 100%;
}
}
}

View File

@ -0,0 +1,247 @@
import { ControlYoutubeDisplayPlaybackMessageComposer, SetYoutubeDisplayPlaylistMessageComposer, YoutubeControlVideoMessageEvent, YoutubeDisplayPlaylist, YoutubeDisplayPlaylistsEvent, YoutubeDisplayVideoMessageEvent } from '@nitrots/nitro-renderer';
import { FC, useCallback, useMemo, useState } from 'react';
import YouTube, { Options } from 'react-youtube';
import { LocalizeText } from '../../../../../api';
import { RoomWidgetUpdateYoutubeDisplayEvent } from '../../../../../api/nitro/room/widgets/events/RoomWidgetUpdateYoutubeDisplayEvent';
import { FurnitureYoutubeDisplayWidgetHandler } from '../../../../../api/nitro/room/widgets/handlers/FurnitureYoutubeDisplayWidgetHandler';
import { BatchUpdates, CreateEventDispatcherHook, CreateMessageHook, SendMessageHook } from '../../../../../hooks';
import { NitroCardContentView, NitroCardGridItemView, NitroCardGridView, NitroCardHeaderView, NitroCardView } from '../../../../../layout';
import { useRoomContext } from '../../../context/RoomContext';
import { YoutubeVideoPlaybackStateEnum } from './utils/YoutubeVideoPlaybackStateEnum';
export const FurnitureYoutubeDisplayView: FC<{}> = props =>
{
const [objectId, setObjectId] = useState(-1);
const [videoId, setVideoId] = useState<string>(null);
const [videoStart, setVideoStart] = useState<number>(null);
const [videoEnd, setVideoEnd] = useState<number>(null);
const [currentVideoState, setCurrentVideoState] = useState(-1);
const [selectedItem, setSelectedItem] = useState<string>(null);
const [playlists, setPlaylists] = useState<YoutubeDisplayPlaylist[]>(null);
const [hasControl, setHasControl] = useState(false);
const [player, setPlayer] = useState<any>(null);
const { eventDispatcher = null } = useRoomContext();
const onRoomWidgetUpdateYoutubeDisplayEvent = useCallback((event: RoomWidgetUpdateYoutubeDisplayEvent) =>
{
switch(event.type)
{
case RoomWidgetUpdateYoutubeDisplayEvent.UPDATE_YOUTUBE_DISPLAY: {
setObjectId(event.objectId);
setHasControl(event.hasControl);
}
}
}, []);
const close = useCallback(() =>
{
setObjectId(-1);
setSelectedItem(null);
setPlaylists(null);
setHasControl(false);
setVideoId(null);
setVideoEnd(null);
setVideoStart(null);
setCurrentVideoState(-1);
}, []);
CreateEventDispatcherHook(RoomWidgetUpdateYoutubeDisplayEvent.UPDATE_YOUTUBE_DISPLAY, eventDispatcher, onRoomWidgetUpdateYoutubeDisplayEvent);
const onVideo = useCallback((event: YoutubeDisplayVideoMessageEvent) =>
{
if(objectId === -1) return;
const parser = event.getParser();
if(objectId !== parser.furniId) return;
BatchUpdates(() =>
{
setVideoId(parser.videoId);
setVideoStart(parser.startAtSeconds);
setVideoEnd(parser.endAtSeconds);
setCurrentVideoState(parser.state);
});
}, [objectId]);
const onPlaylists = useCallback((event: YoutubeDisplayPlaylistsEvent) =>
{
if(objectId === -1) return;
const parser = event.getParser();
if(objectId !== parser.furniId) return;
BatchUpdates(() =>
{
setPlaylists(parser.playlists);
setSelectedItem(parser.selectedPlaylistId);
setVideoId(null);
setCurrentVideoState(-1);
setVideoEnd(null);
setVideoStart(null);
});
}, [objectId]);
const onControlVideo = useCallback((event: YoutubeControlVideoMessageEvent) =>
{
if(objectId === -1) return;
const parser = event.getParser();
if(objectId !== parser.furniId) return;
switch(parser.commandId)
{
case 1:
setCurrentVideoState(YoutubeVideoPlaybackStateEnum.PLAYING);
if(player.getPlayerState() !== YoutubeVideoPlaybackStateEnum.PLAYING)
player.playVideo();
break;
case 2:
setCurrentVideoState(YoutubeVideoPlaybackStateEnum.PAUSED);
if(player.getPlayerState() !== YoutubeVideoPlaybackStateEnum.PAUSED)
player.pauseVideo();
break;
}
}, [objectId, player]);
CreateMessageHook(YoutubeDisplayVideoMessageEvent, onVideo);
CreateMessageHook(YoutubeDisplayPlaylistsEvent, onPlaylists);
CreateMessageHook(YoutubeControlVideoMessageEvent, onControlVideo);
const processAction = useCallback((action: string) =>
{
switch(action)
{
case 'playlist_prev':
SendMessageHook(new ControlYoutubeDisplayPlaybackMessageComposer(objectId, FurnitureYoutubeDisplayWidgetHandler.CONTROL_COMMAND_PREVIOUS_VIDEO));
break;
case 'playlist_next':
SendMessageHook(new ControlYoutubeDisplayPlaybackMessageComposer(objectId, FurnitureYoutubeDisplayWidgetHandler.CONTROL_COMMAND_NEXT_VIDEO));
break;
case 'video_pause':
if(hasControl && videoId && videoId.length)
{
SendMessageHook(new ControlYoutubeDisplayPlaybackMessageComposer(objectId, FurnitureYoutubeDisplayWidgetHandler.CONTROL_COMMAND_PAUSE_VIDEO));
}
break;
case 'video_play':
if(hasControl && videoId && videoId.length)
{
SendMessageHook(new ControlYoutubeDisplayPlaybackMessageComposer(objectId, FurnitureYoutubeDisplayWidgetHandler.CONTROL_COMMAND_CONTINUE_VIDEO));
}
break;
default:
if(selectedItem === action)
{
setSelectedItem(null);
SendMessageHook(new SetYoutubeDisplayPlaylistMessageComposer(objectId, ''));
return;
}
SendMessageHook(new SetYoutubeDisplayPlaylistMessageComposer(objectId, action));
setSelectedItem(action);
}
}, [hasControl, objectId, selectedItem, videoId]);
const onReady = useCallback((event: any) =>
{
setPlayer(event.target);
}, []);
const onStateChange = useCallback((event: any) =>
{
setPlayer(event.target);
if(objectId)
{
switch(event.target.getPlayerState())
{
case -1:
case 1:
if(currentVideoState === 2)
{
//event.target.pauseVideo();
}
if(currentVideoState !== 1)
{
processAction('video_play');
}
return;
case 2:
if(currentVideoState !== 2)
{
processAction('video_pause');
}
}
}
}, [currentVideoState, objectId, processAction]);
const getYoutubeOpts = useMemo( () =>
{
if(!videoStart && !videoEnd)
{
return {
height: '375',
width: '500',
playerVars: {
autoplay: 1,
disablekb: 1,
controls: 0,
origin: window.origin,
modestbranding: 1
}
}
}
return {
height: '375',
width: '500',
playerVars: {
autoplay: 1,
disablekb: 1,
controls: 0,
origin: window.origin,
modestbranding: 1,
start: videoStart,
end: videoEnd
}
}
}, [videoEnd, videoStart]);
if((objectId === -1)) return null;
return (
<NitroCardView className="youtube-tv-widget">
<NitroCardHeaderView headerText={''} onCloseClick={close} />
<NitroCardContentView>
<div className="row w-100 h-100">
<div className="youtube-video-container col-9">
{(videoId && videoId.length > 0) &&
<YouTube videoId={videoId} opts={getYoutubeOpts as Options} onReady={onReady} onStateChange={onStateChange} containerClassName={'youtubeContainer'} />
}
{(!videoId || videoId.length === 0) &&
<div className="empty-video w-100 h-100 justify-content-center align-items-center d-flex">{LocalizeText('widget.furni.video_viewer.no_videos')}</div>
}
</div>
<div className="playlist-container col-3">
<span className="playlist-controls justify-content-center d-flex">
<i className="icon icon-youtube-prev cursor-pointer" onClick={() => processAction('playlist_prev')} />
<i className="icon icon-youtube-next cursor-pointer" onClick={() => processAction('playlist_next')} />
</span>
<div className="mb-1">{LocalizeText('widget.furni.video_viewer.playlists')}</div>
<NitroCardGridView columns={1} className="playlist-grid">
{playlists && playlists.map((entry, index) =>
{
return (
<NitroCardGridItemView key={index} onClick={() => processAction(entry.video)} itemActive={entry.video === selectedItem}>
<b>{entry.title}</b> - {entry.description}
</NitroCardGridItemView>
)
})}
</NitroCardGridView>
</div>
</div>
</NitroCardContentView>
</NitroCardView>
)
}

View File

@ -0,0 +1,9 @@
export class YoutubeVideoPlaybackStateEnum
{
public static readonly UNSTARTED = -1;
public static readonly ENDED = 0;
public static readonly PLAYING = 1;
public static readonly PAUSED = 2;
public static readonly BUFFERING = 3;
public static readonly CUED = 5;
}