@fortawesome/free-solid-svg-icons#faPlay TypeScript Examples
The following examples show how to use
@fortawesome/free-solid-svg-icons#faPlay.
You can vote up the ones you like or vote down the ones you don't like,
and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: IconLibrary.ts From react-memory-game with MIT License | 6 votes |
library.add(
faChevronLeft,
faPlay,
faPause,
faUndo,
faClock,
faQuestionCircle,
faTimes,
faPalette,
// Icons for the game
faPoo,
faAnchor,
faMoon,
faAppleAlt,
faBell,
faBible,
faBomb,
faBone,
faCar,
faCat,
faChess,
faSkull,
faFeatherAlt,
faFire,
faHeart,
faMusic,
)
Example #2
Source File: VoiceLinePlayer.tsx From apps with MIT License | 6 votes |
render() {
const command = this.state.playing ? "Stop" : "Play";
const title = `${command} ${this.props.title}`;
return (
<Button
variant={this.state.playing ? "warning" : "success"}
onClick={this.onClick}
className="voice-line-player-button"
title={title}
>
<FontAwesomeIcon icon={this.state.playing ? faStop : faPlay} />
{this.props.showTitle ? <> {title}</> : null}
</Button>
);
}
Example #3
Source File: ItemListedPlayStatus.tsx From sync-party with GNU General Public License v3.0 | 6 votes |
export default function ItemListedPlayStatus({
isCurrentlyPlayingItem,
hovering,
isPlaying
}: Props): ReactElement | null {
return isCurrentlyPlayingItem && !hovering ? (
isPlaying ? (
<div className="my-auto ml-2">
<FontAwesomeIcon icon={faPlay} size="xs"></FontAwesomeIcon>
</div>
) : (
<div className="my-auto ml-2">
<FontAwesomeIcon icon={faPause} size="xs"></FontAwesomeIcon>
</div>
)
) : null;
}
Example #4
Source File: AudioPlayer.tsx From frontend.ro with MIT License | 5 votes |
export default function AudioPlayer({ title, src, className } : Props) {
const ref = useRef<HTMLAudioElement>(null);
const [isPlaying, setIsPlaying] = useState(false);
const onPlay = () => { setIsPlaying(true); };
const onPause = () => { setIsPlaying(false); };
const togglePlay = () => {
const { paused } = ref.current;
if (paused) {
ref.current.play();
} else {
ref.current.pause();
}
};
const stop = () => {
ref.current.pause();
ref.current.currentTime = 0;
};
useEffect(() => {
ref.current.addEventListener('play', onPlay);
ref.current.addEventListener('pause', onPause);
return () => {
ref.current.removeEventListener('play', onPlay);
ref.current.removeEventListener('pause', onPause);
};
}, []);
return (
<div className={`${styles['audio-player']} ${className} d-flex align-items-center`}>
<Button onClick={togglePlay} className={styles['play-button']}>
<FontAwesomeIcon icon={isPlaying ? faPause : faPlay} />
</Button>
<Button className={`${styles['stop-button']}${isPlaying ? ` ${styles['stop-button--visible']}` : ''}`} onClick={stop}>
<FontAwesomeIcon icon={faStop} />
</Button>
<p title={title} className="text-white">{title}</p>
{/* eslint-disable-next-line jsx-a11y/media-has-caption */}
<audio ref={ref} src={src} hidden />
</div>
);
}
Example #5
Source File: ExercisePreview.tsx From frontend.ro with MIT License | 5 votes |
function ExercisePreview({
href,
viewMode,
isPrivate = false,
readOnly,
feedbackCount,
isApproved,
exercise,
className = '',
}: Props) {
const { btnText, infoMessage } = getFooterTexts(viewMode, feedbackCount, readOnly, isApproved);
let computedClassName = `${styles['exercise-preview']} bg-white rounded-md`;
if (isApproved) {
computedClassName += ` ${styles['is--done']}`;
}
if (!isApproved && readOnly) {
computedClassName += ` ${styles['is--waiting']}`;
}
if (feedbackCount > 0) {
computedClassName += ` ${styles['has--issues']}`;
}
return (
<div className={`${computedClassName} ${className}`}>
<header className="text-right">
{isPrivate && <FontAwesomeIcon className="text-grey" width="24" icon={faLock} title="Exercițiu privat" />}
</header>
<Markdown
className={`${styles.body} relative overflow-hidden`}
markdownString={exercise.body}
variant="transparent"
/>
<footer className="d-flex align-items-center justify-content-between flex-wrap">
<>
{viewMode === 'STUDENT' && infoMessage && (
<span>
{infoMessage}
</span>
)}
{(viewMode === 'TEACHER' || !infoMessage) && (
<>
<Link href={`/${exercise.user.username}`}>
<a className={styles.avatar}>
<img src={exercise.user.avatar} alt="Author avatar" />
</a>
</Link>
<div className={`${styles.tags} truncate d-inline-block`}>
{exercise.tags.map((t) => (
<span className="text-bold" key={t}>
{t}
</span>
))}
</div>
</>
)}
</>
<Link href={href}>
<a className={`d-flex btn no-underline ${isApproved || readOnly || feedbackCount > 0 ? 'btn--light' : 'btn--blue'}`}>
{btnText}
<FontAwesomeIcon width="16" className="ml-2" icon={faPlay} />
</a>
</Link>
</footer>
</div>
);
}
Example #6
Source File: icon.service.ts From WowUp with GNU General Public License v3.0 | 5 votes |
public constructor(private _matIconRegistry: MatIconRegistry, private _sanitizer: DomSanitizer) {
this.addSvg(faAngleDoubleDown);
this.addSvg(faArrowUp);
this.addSvg(faArrowDown);
this.addSvg(faSyncAlt);
this.addSvg(faTimes);
this.addSvg(faExternalLinkAlt);
this.addSvg(faQuestionCircle);
this.addSvg(faPlay);
this.addSvg(faClock);
this.addSvg(faBug);
this.addSvg(faLink);
this.addSvg(faDiscord);
this.addSvg(faGithub);
this.addSvg(faInfoCircle);
this.addSvg(faCodeBranch);
this.addSvg(faCaretDown);
this.addSvg(faExclamationTriangle);
this.addSvg(faCode);
this.addSvg(faPatreon);
this.addSvg(faCoins);
this.addSvg(faCompressArrowsAlt);
this.addSvg(faPencilAlt);
this.addSvg(faCheckCircle);
this.addSvg(faDiceD6);
this.addSvg(faSearch);
this.addSvg(faInfoCircle);
this.addSvg(faNewspaper);
this.addSvg(faCog);
this.addSvg(faAngleUp);
this.addSvg(faAngleDown);
this.addSvg(faChevronRight);
this.addSvg(faUserCircle);
this.addSvg(faEllipsisV);
this.addSvg(faCopy);
this.addSvg(farCheckCircle);
this.addSvg(faExclamation);
this.addSvg(faTrash);
this.addSvg(faHistory);
this.addSvg(faCaretSquareRight);
this.addSvg(faCaretSquareLeft);
this.addSvg(faMinimize);
this.addSvg(faUpRightFromSquare);
}
Example #7
Source File: RunTimeTools.tsx From MagicUI with Apache License 2.0 | 5 votes |
export default function RunTimeTools() {
const openFileItems = useSelector((state: IStoreState) => state.openFileItems);
const files = useSelector((state: IStoreState) => state.dslFileArray)
const user = useSelector((state: IStoreState) => state.user);
const handleRun = () => {
Bridge.compile('json', openFileItems.items[openFileItems.currentIndex].code).then(v => {
Bridge.open(WidgetType.WEBGL, v);
})
};
const handleSave = () => {
const cur = openFileItems.items[openFileItems.currentIndex];
saveDslCode(cur.id, user.email, cur.code, cur.fileId).then((v) => {
if (!v.err) {
toast('save code!');
}
})
};
const handleSaveAll = () => {
saveAllDslCode(user.email, files.files).then((v) => {
if (!v.err) {
toast('save all!');
}
});
};
const handleBuild = () => {
Bridge.open(WidgetType.CODE, {
type: 'target',
data: openFileItems.items[openFileItems.currentIndex].code
});
};
return (
<div className={style.run_tools}>
<button className={style.build_btn} onClick={handleBuild}>
<FontAwesomeIcon icon={faGavel}/>
</button>
<button className={style.run_btn} onClick={handleRun}>
<FontAwesomeIcon icon={faPlay}/>
</button>
<button className={style.save_btn} onClick={handleSave}>
<FontAwesomeIcon icon={faFileCode}/>
</button>
<button className={style.save_all_btn} onClick={handleSaveAll}>
<FontAwesomeIcon icon={faSave}/>
</button>
<div className={style.search}>
<input/>
<FontAwesomeIcon icon={faSearch}/>
</div>
</div>
);
}
Example #8
Source File: RuntimeTools.tsx From MagicUI with Apache License 2.0 | 5 votes |
export default function RuntimeTools(props: {}) {
const {loading} = useSelector((state: IStoreState) => state.autoSaveLoading);
console.log('loading', loading);
const loadingIcon = (
<FontAwesomeIcon icon={faCircleNotch} spin color={'gray'}/>
);
const checkIcon = (
<FontAwesomeIcon icon={faCheck} color={'red'}/>
);
const dispatch = useDispatch();
const build = () => {
dispatch(buildCode());
};
const run = () => {
modal(cancel => (
<div onClick={cancel}>
Cancel
</div>
));
};
const handleExport = () => {
dispatch(exportCode());
};
return (
<div className={style.run_tools}>
<div className={style.label}>
RUN TOOLS:
</div>
<div className={style.tools}>
<button className={style.build_btn} onClick={build}>
<FontAwesomeIcon icon={faGavel}/>
</button>
<button className={style.run_btn} onClick={run}>
<FontAwesomeIcon icon={faPlay}/>
</button>
<button className={style.export_btn} onClick={handleExport}>
<FontAwesomeIcon icon={faFileExport}/>
</button>
<button className={style.check_btn}>
{ loading ? loadingIcon : checkIcon }
</button>
</div>
</div>
);
}
Example #9
Source File: SimulationResultDisplay.tsx From calculate-my-odds with MIT License | 4 votes |
render() {
const result = this.state.simulationResult;
const tabs = this.getTabs();
const renderProbabilityOfCompletionInfoBox = (label: string, result?: SimulationObjectiveResult) => (
<InfoBox
label={label}
content={this.getProbabilityOfCompletingGoalContent(result)}
/>
);
const renderAverageInfoBox = (label: string, result?: SimulationObjectiveResult) => (
<InfoBox
label={label}
content={this.getAverageIterationsPerCompletionContent(result)}
/>
);
const renderIterationsUntilProbabilityInfoBox = (appendLabel: string, result?: SimulationObjectiveResult) => (
<InfoBox
label={(
<span>
Iterations until {" "}
<EditableNumber
initialValue={initialIterationsAtProbability * 100}
append="%"
onChange={value => this.simulationHandler.requestIterationsAtProbability(value / 100)}
validate={value => value >= 0 && value <= 100}
/>
{" "} {appendLabel}
</span>
)}
content={this.getIterationsAtProbabilityContent(result)}
/>
);
const renderProbabilityUntilIterationsInfoBox = (prependLabel: string, result?: SimulationObjectiveResult) => (
<InfoBox
label={(
<span>
{prependLabel} {" "}
<EditableInteger
initialValue={initialProbabilityAtIterations}
onChange={value => this.simulationHandler.requestProbabilityAtIterations(value)}
validate={value => value >= 0}
/>
{" "} iterations
</span>
)}
content={this.getProbabilityAtIterationsContent(result)}
/>
);
return (
<div className="simulation-result-display-component">
<SpaceContainer className="result-info">
{(this.state.simulationResult?.unknownResults ?? 0) > 0 &&
<div style={{textAlign: "center"}}>
<WarningDisplay>
Total amount of iterations exceeded {abbreviateValue(this.state.simulationResult!.maxIterations)} in some of the rounds.
The simulator does not simulate past that, so the results may be inaccurate.
</WarningDisplay>
</div>
}
<InfoBox
label="Simulated rounds"
content={(
<div className="iterations-content-container">
<TooltipContainer tooltipContent={result?.totalRounds.toLocaleString()} showOnHover={result !== undefined}>
<div className="iterations-content">
{result ? abbreviateValue(result.totalRounds, true, true) : "-"}
</div>
</TooltipContainer>
<div className="iterations-icon">
<TooltipContainer tooltipContent={this.state.isRunning ? "Pause" : "Play"} showOnHover>
<IconContainer
icon={this.state.isRunning ? faPause : faPlay}
onClick={() => {
if (this.state.isRunning) {
this.pauseSimulation();
}
else {
this.resumeSimulation();
}
}}
/>
</TooltipContainer>
</div>
</div>
)}
/>
{(this.state.simulationResult?.unknownResults ?? 0) > 0 &&
<InfoBox
label="Unknown results"
content={(
<div className="unknowns-content-container">
<TooltipContainer
tooltipContent={this.state.simulationResult!.unknownResults.toLocaleString()}
side={TooltipSide.Top}
showOnHover
>
{this.getUnknownResultsContent()}
</TooltipContainer>
</div>
)}
/>
}
{this.state.currentSimulationFailureRoot.failures.length === 0 ?
<>
{renderAverageInfoBox("Average iterations to complete goals", this.state.simulationResult?.successResult)}
{renderIterationsUntilProbabilityInfoBox("chance of completing goals", this.state.simulationResult?.successResult)}
{renderProbabilityUntilIterationsInfoBox("Chance of completing goals after", this.state.simulationResult?.successResult)}
</>
:
tabs ?
<Tabs
selectedIndex={this.state.selectedResultIndex}
onTabSelected={index => this.setState({ selectedResultIndex: index })}
tabs={tabs.map(tab => ({
id: tab.name,
name: tab.name,
content: (
<SpaceContainer>
{renderProbabilityOfCompletionInfoBox(tab.probabilityOfCompletionText, tab.result)}
{renderAverageInfoBox(tab.averageText, tab.result)}
{renderIterationsUntilProbabilityInfoBox(tab.appendIterationsUntilProbabilityText, tab.result)}
{renderProbabilityUntilIterationsInfoBox(tab.prependProbabilityUntilIterationsText, tab.result)}
</SpaceContainer>
)
}))}
/>
: null
}
<div>
<Button
content="Clear result"
onClick={this.props.onRequestNewCalculation}
/>
</div>
</SpaceContainer>
<div className="result-chart">
<CumulativeSuccessChart
dataSets={this.getDataSets()}
/>
</div>
</div>
);
}
Example #10
Source File: LoadSave.tsx From ble with Apache License 2.0 | 4 votes |
DomApp: FunctionComponent = () => {
const { level } = useStore();
function onSave(): void {
// don't want invalid entities to end up in the snapshot
level.cleanInvalidEntities();
const snapshot = getSnapshot(level);
try {
validateLevel(snapshot);
} catch (err) {
// eslint-disable-next-line no-console
alert(`Error: your level contains invalid elements. Come to https://discord.gg/KEb4wSN for help!
${err instanceof Error ? err.message : JSON.stringify(err)}`);
}
const filename = toFilename(level.name, 'json');
const snapshotStr = JSON.stringify(snapshot, null, '\t') + '\n';
const blob = new Blob([snapshotStr], { type: 'application/json; charset=utf-8' });
saveAs(blob, filename);
}
function onLoad(ev: ChangeEvent<HTMLInputElement>): void {
if (ev.target.files === null || ev.target.files.length < 1) return;
const reader = new FileReader();
reader.addEventListener('load', (ev_) => {
if (ev_.target === null) return;
try {
const snapshot = JSON.parse(ev_.target.result as string);
const actualSnapshot = levelPreProcessor(snapshot);
// we have to patch the snapshot here because of this bug https://github.com/mobxjs/mobx-state-tree/issues/1317
// @ts-expect-error
applySnapshot(level, actualSnapshot);
} catch (err) {
// eslint-disable-next-line no-console
console.error(err);
alert('Invalid file');
}
});
reader.readAsText(ev.target.files[0]);
}
type SendParams = {
type: 'loadLevel' | 'uploadLevel'
};
function sendLevelToGame(params: SendParams): void {
// don't want invalid entities to end up in the snapshot
level.cleanInvalidEntities();
const snapshot = getSnapshot(level);
try {
validateLevel(snapshot);
} catch (err) {
// eslint-disable-next-line no-console
console.error(err);
alert(`Error: your level contains invalid elements. Come to https://discord.gg/KEb4wSN for help!
${err instanceof Error ? err.message : JSON.stringify(err)}`);
return;
}
postMessage({
...params,
level: snapshot,
});
}
function onTest(): void {
sendLevelToGame({
type: 'loadLevel',
});
}
function onUpload(): void {
sendLevelToGame({
type: 'uploadLevel',
});
}
return (
<Fragment>
{inIframe && (
<Fragment>
<Button onClick={onTest}>
<FontAwesomeIcon icon={faPlay}/>
 
Test
</Button>
<Button onClick={onUpload}>
<FontAwesomeIcon icon={faUpload}/>
 
Upload
</Button>
</Fragment>
)}
<FilePicker>
<FontAwesomeIcon icon={faFolderOpen}/>
 
Import<input accept="application/json" type="file" onChange={onLoad}/></FilePicker>
<Button onClick={onSave}>
<FontAwesomeIcon icon={faSave}/>
 
Export
</Button>
</Fragment>
);
}
Example #11
Source File: index.tsx From nouns-monorepo with GNU General Public License v3.0 | 4 votes |
NavBar = () => {
const activeAccount = useAppSelector(state => state.account.activeAccount);
const stateBgColor = useAppSelector(state => state.application.stateBackgroundColor);
const isCool = useAppSelector(state => state.application.isCoolBackground);
const history = useHistory();
const ethBalance = useEtherBalance(config.addresses.nounsDaoExecutor);
const lidoBalanceAsETH = useLidoBalance();
const treasuryBalance = ethBalance && lidoBalanceAsETH && ethBalance.add(lidoBalanceAsETH);
const daoEtherscanLink = buildEtherscanHoldingsLink(config.addresses.nounsDaoExecutor);
const [isNavExpanded, setIsNavExpanded] = useState(false);
const useStateBg =
history.location.pathname === '/' ||
history.location.pathname.includes('/noun/') ||
history.location.pathname.includes('/auction/');
const nonWalletButtonStyle = !useStateBg
? NavBarButtonStyle.WHITE_INFO
: isCool
? NavBarButtonStyle.COOL_INFO
: NavBarButtonStyle.WARM_INFO;
const closeNav = () => setIsNavExpanded(false);
return (
<>
<Navbar
expand="xl"
style={{ backgroundColor: `${useStateBg ? stateBgColor : 'white'}` }}
className={classes.navBarCustom}
expanded={isNavExpanded}
>
<Container style={{ maxWidth: 'unset' }}>
<div className={classes.brandAndTreasuryWrapper}>
<Navbar.Brand as={Link} to="/" className={classes.navBarBrand}>
<img src={logo} className={classes.navBarLogo} alt="Nouns DAO logo" />
</Navbar.Brand>
{Number(CHAIN_ID) !== 1 && (
<Nav.Item>
<img className={classes.testnetImg} src={testnetNoun} alt="testnet noun" />
TESTNET
</Nav.Item>
)}
<Nav.Item>
{treasuryBalance && (
<Nav.Link
href={daoEtherscanLink}
className={classes.nounsNavLink}
target="_blank"
rel="noreferrer"
>
<NavBarTreasury
treasuryBalance={Number(utils.formatEther(treasuryBalance)).toFixed(0)}
treasuryStyle={nonWalletButtonStyle}
/>
</Nav.Link>
)}
</Nav.Item>
</div>
<Navbar.Toggle className={classes.navBarToggle} aria-controls="basic-navbar-nav" onClick={() => setIsNavExpanded(!isNavExpanded)} />
<Navbar.Collapse className="justify-content-end">
<Nav.Link as={Link} to="/vote" className={classes.nounsNavLink} onClick={closeNav} >
<NavBarButton
buttonText={<Trans>DAO</Trans>}
buttonIcon={<FontAwesomeIcon icon={faUsers} />}
buttonStyle={nonWalletButtonStyle}
/>
</Nav.Link>
<Nav.Link
href={externalURL(ExternalURL.notion)}
className={classes.nounsNavLink}
target="_blank"
rel="noreferrer"
onClick={closeNav}
>
<NavBarButton
buttonText={<Trans>Docs</Trans>}
buttonIcon={<FontAwesomeIcon icon={faBookOpen} />}
buttonStyle={nonWalletButtonStyle}
/>
</Nav.Link>
<Nav.Link
href={externalURL(ExternalURL.discourse)}
className={classes.nounsNavLink}
target="_blank"
rel="noreferrer"
onClick={closeNav}
>
<NavBarButton
buttonText={<Trans>Discourse</Trans>}
buttonIcon={<FontAwesomeIcon icon={faComments} />}
buttonStyle={nonWalletButtonStyle}
/>
</Nav.Link>
<Nav.Link as={Link} to="/playground" className={classes.nounsNavLink} onClick={closeNav}>
<NavBarButton
buttonText={<Trans>Playground</Trans>}
buttonIcon={<FontAwesomeIcon icon={faPlay} />}
buttonStyle={nonWalletButtonStyle}
/>
</Nav.Link>
<NavLocaleSwitcher buttonStyle={nonWalletButtonStyle} />
<NavWallet address={activeAccount || '0'} buttonStyle={nonWalletButtonStyle} />{' '}
</Navbar.Collapse>
</Container>
</Navbar>
</>
);
}
Example #12
Source File: MediaPlayerContainer.tsx From sync-party with GNU General Public License v3.0 | 4 votes |
export default function MediaPlayerContainer({ socket }: Props): JSX.Element {
// Constants
const uiTimeoutIntervalResolution = 500;
const uiTimeoutShortDelay = 5000;
const uiTimeoutLongDelay = 30000;
const syncStatusIntervalDelay = 1000;
const syncStatusIntervalTolerance = 1500;
const actionMessageDelay = 3000;
const seekStepSize = 5;
const volumeStepSize = 0.05;
// Utilities
const dispatch = useDispatch();
const { t } = useTranslation();
// Data from global state
const party = useSelector((state: RootAppState) => state.globalState.party);
const user = useSelector((state: RootAppState) => state.globalState.user);
const uiVisible = useSelector(
(state: RootAppState) => state.globalState.uiVisible
);
const initialServerTimeOffset = useSelector(
(state: RootAppState) => state.globalState.initialServerTimeOffset
);
const webRtcGlobalState = useSelector(
(state: RootAppState) => state.globalState.webRtc
);
// Local states & their refs
const [reactPlayer, setReactPlayer] = useState<ReactPlayer>();
const [joinedParty, setJoinedParty] = useState(false);
const [freshlyJoined, setFreshlyJoined] = useState(true);
const [windowHeight, setWindowHeight] = useState(window.innerHeight);
const [hasLastPosition, setHasLastPosition] = useState(false);
const initialPlayerState = {
playOrder: null,
isPlaying: false,
isFocused: true,
isSeeking: false,
isFullScreen: false,
isSyncing: false,
isBuffering: false,
playlistIndex: 0,
playingItem: useSelector(
(state: RootAppState) => state.globalState.playingItem
),
duration: 0,
sourceUrl: '',
volume: 1
};
const playerStateReducer = (
playerState: PlayerState,
updatedProperties: PlayerStateActionProperties
): PlayerState => {
return { ...playerState, ...updatedProperties };
};
const [playerState, setPlayerState] = useReducer(
playerStateReducer,
initialPlayerState
);
const playerStateRef = useRef(playerState);
playerStateRef.current = playerState;
const initialPlayerTimeoutState = {
actionMessageTimeoutId: null,
actionMessageTimeoutDone: false,
uiTimeoutId: null,
uiTimeoutDelay: uiTimeoutShortDelay,
uiTimeoutTimestamp: Date.now()
};
const playerTimeoutReducer = (
playerTimeoutState: PlayerTimeoutState,
updatedProperties: PlayerTimeoutStateActionProperties
): PlayerTimeoutState => {
return { ...playerTimeoutState, ...updatedProperties };
};
const [playerTimeoutState, setPlayerTimeoutState] = useReducer(
playerTimeoutReducer,
initialPlayerTimeoutState
);
const playerTimeoutStateRef = useRef(playerTimeoutState);
playerTimeoutStateRef.current = playerTimeoutState;
// Clear all timeouts
const clearAllTimeouts = (): void => {
if (playerTimeoutStateRef.current.uiTimeoutId) {
clearTimeout(playerTimeoutStateRef.current.uiTimeoutId);
}
if (playerTimeoutStateRef.current.actionMessageTimeoutId) {
clearTimeout(playerTimeoutStateRef.current.actionMessageTimeoutId);
}
};
// Player functions
const handleKeyboardInput = (event: KeyboardEvent): void => {
if (playerState.isFocused && reactPlayer) {
handleKeyCommands(
reactPlayer,
event,
handlePlayPause,
handleFullScreen,
playerState,
volumeStepSize,
setPlayerState,
seekStepSize,
emitPlayWish
);
}
};
const getCurrentPosition = (): number | undefined => {
if (reactPlayer) {
return reactPlayer.getCurrentTime() / reactPlayer.getDuration();
} else {
return undefined;
}
};
// Playback functions
const emitPlayWish = (
mediaItem: MediaItem,
isPlaying: boolean,
lastPositionItemId: string | null,
requestLastPosition: boolean,
newPosition?: number,
noIssuer?: boolean,
direction?: 'left' | 'right'
): void => {
if (socket && party && user) {
const playWish: PlayWish = {
partyId: party.id,
issuer: noIssuer ? 'system' : user.id,
mediaItemId: mediaItem.id,
type: mediaItem.type,
isPlaying: isPlaying,
position:
newPosition !== undefined
? newPosition
: reactPlayer
? reactPlayer.getCurrentTime() /
reactPlayer.getDuration()
: 0,
lastPosition: lastPositionItemId
? {
itemId: lastPositionItemId,
position:
reactPlayer &&
reactPlayer.getDuration() > 300 &&
!noIssuer
? reactPlayer.getCurrentTime() /
reactPlayer.getDuration()
: 0
}
: null,
requestLastPosition: requestLastPosition,
timestamp: Date.now()
};
if (direction) {
playWish.direction = direction;
}
socket.emit('playWish', playWish);
}
};
// Point currently playing item index to the right item in the playlist
const updatePlaylistIndex = useCallback(
(playlistItem: MediaItem): void => {
if (party && party.items.length) {
const index = party.items.findIndex(
(listItem: MediaItem) => listItem.id === playlistItem.id
);
setPlayerState({ playlistIndex: index });
}
},
[party]
);
// Effects
// Attach key event listener
useEffect(() => {
document.addEventListener('keydown', handleKeyboardInput);
return (): void => {
document.removeEventListener('keydown', handleKeyboardInput);
};
});
// Attach Fullscreen detector to window
const handleResize = (): void => {
setWindowHeight(window.innerHeight);
};
useEffect(() => {
window.addEventListener('resize', handleResize);
window.addEventListener('fullscreenchange', handleResize);
return (): void => {
window.removeEventListener('resize', handleResize);
window.removeEventListener('fullscreenchange', handleResize);
};
}, []);
useEffect(() => {
if (
windowHeight > screen.height - 5 &&
windowHeight < screen.height + 5
) {
setPlayerState({ isFullScreen: true });
} else {
setPlayerState({ isFullScreen: false });
}
}, [windowHeight]);
// Update playlist index if playingItem in global state changes
useEffect(() => {
if (playerState.playingItem) {
updatePlaylistIndex(playerState.playingItem);
}
}, [playerState.playingItem, updatePlaylistIndex]);
// Emit joinParty when everything is set up; subscribe to play orders; subscribe to syncStatus updates
useEffect(() => {
if (socket && party && user) {
if (!joinedParty) {
socket.emit('joinParty', {
userId: user.id,
partyId: party.id,
timestamp: Date.now()
});
setJoinedParty(true);
}
// Socket 1/3: PlayOrders
socket.off('playOrder');
socket.on('playOrder', (playOrder: PlayOrder) => {
setPlayerState({ playOrder: playOrder });
setHasLastPosition(false);
const playOrderItem = party.items.find((item: MediaItem) => {
return item.id === playOrder.mediaItemId;
});
if (playOrderItem) {
// Set React Player source URL
let newSourceUrl;
if (playOrder.type === 'file') {
newSourceUrl =
'/api/file/' +
playOrder.mediaItemId +
'?party=' +
party.id;
} else {
newSourceUrl = playOrderItem.url;
}
// Action message
let actionMessageIcon = faClock; // Default case: seeking
if (
!playerStateRef.current.playingItem ||
(playerStateRef.current.playingItem &&
playOrder.mediaItemId !==
playerStateRef.current.playingItem.id)
) {
actionMessageIcon = faExchangeAlt; // Media item change
} else if (
playOrder.isPlaying !== playerStateRef.current.isPlaying
) {
if (playOrder.isPlaying === true) {
actionMessageIcon = faPlay; // Pause -> Play
} else if (playOrder.isPlaying === false) {
actionMessageIcon = faPause; // Play -> Pause
}
} else {
if (playOrder.direction) {
if (playOrder.direction === 'left') {
actionMessageIcon = faLongArrowAltLeft; // Seek left
} else if (playOrder.direction === 'right') {
actionMessageIcon = faLongArrowAltRight; // Seek right
}
}
}
// None found if issuer === 'system' -> No action message
const memberInParty = party.members.find(
(member: ClientPartyMember) =>
member.id === playOrder.issuer
);
if (memberInParty) {
const actionMessageContent = (
<ActionMessageContent
text={memberInParty.username}
icon={actionMessageIcon}
></ActionMessageContent>
);
dispatch(
setGlobalState({
actionMessage: {
text: actionMessageContent
}
})
);
}
setPlayerState({
playingItem: playOrderItem,
sourceUrl: newSourceUrl,
isSyncing: true
});
dispatch(
setGlobalState({
playingItem: playOrderItem
})
);
updatePlaylistIndex(playOrderItem);
}
});
// Socket 2/3: Receive Sync status
socket.off('syncStatus');
socket.on('syncStatus', (syncStatus: SyncStatusIncomingMessage) => {
const syncStatusStateNew = [] as SyncStatusReceiveMember[];
const memberStatusStateNew: MemberStatus = {};
Object.keys(syncStatus).forEach((memberId) => {
// 1. Set online status of each member
memberStatusStateNew[memberId] = {
online: false,
serverTimeOffset: syncStatus[memberId].serverTimeOffset,
webRtc: syncStatus[memberId].webRtc
};
if (
syncStatus[user.id] &&
syncStatus[memberId] &&
syncStatus[memberId].timestamp +
syncStatus[memberId].serverTimeOffset >
Date.now() +
syncStatus[user.id].serverTimeOffset -
syncStatusIntervalTolerance
) {
memberStatusStateNew[memberId].online = true;
}
// 2. Calculate delay for every party member who's not us
if (syncStatus[user.id] && memberId !== user.id) {
const delta = calculateSyncDelta(
syncStatus,
playerStateRef,
user,
memberId
);
const memberInParty = party.members.find(
(member: ClientPartyMember) =>
member.id === memberId
);
if (memberInParty) {
syncStatusStateNew.push({
id: memberId,
delta: delta,
username: memberInParty.username
});
}
}
});
dispatch(
setGlobalState({
syncStatus: syncStatusStateNew,
memberStatus: memberStatusStateNew
})
);
});
}
return (): void => {
clearAllTimeouts();
};
}, [socket, party, user, joinedParty, dispatch, updatePlaylistIndex]);
// Sync procedure finish: Seek, isPlaying, start buffering state
useEffect(() => {
if (
reactPlayer &&
reactPlayer.getInternalPlayer() &&
playerState.duration &&
playerState.playOrder &&
playerState.isSyncing &&
playerState.playingItem
) {
let offset = 0;
if (freshlyJoined) {
if (playerState.playOrder.isPlaying) {
offset =
(Date.now() +
initialServerTimeOffset -
playerState.playOrder.timestamp) /
(playerState.duration * 1000);
}
setFreshlyJoined(false);
}
if (playerState.playOrder.lastPosition) {
setHasLastPosition(true);
} else {
setHasLastPosition(false);
}
reactPlayer.seekTo(playerState.playOrder.position + offset);
const site = getSite(playerState.playingItem.url);
setPlayerState({
isSeeking: false,
isPlaying: playerState.playOrder.isPlaying,
isSyncing: false,
isBuffering:
playerState.playOrder.isPlaying &&
playerState.playOrder.type === 'web' &&
(site === 'youtube' ||
site === 'facebook' ||
playerState.playingItem.type === 'file')
});
}
}, [
reactPlayer,
playerState,
initialServerTimeOffset,
freshlyJoined,
dispatch
]);
// Socket 3/3: Emit syncStatus in intervals
useInterval(() => {
if (socket && user && party) {
const syncStatus: SyncStatusOutgoingMessage = {
partyId: party.id,
userId: user.id,
timestamp: Date.now(),
position: reactPlayer
? reactPlayer.getCurrentTime() / reactPlayer.getDuration()
: 0,
isPlaying: playerStateRef.current.isPlaying,
webRtc: webRtcGlobalState
};
socket.emit('syncStatus', syncStatus);
}
}, syncStatusIntervalDelay);
// React Player Event handlers
const handleDuration = (duration: number): void => {
setPlayerState({ duration: duration });
};
const handleProgress = (reactPlayerState: ReactPlayerState): void => {
if (!playerState.isSeeking) {
dispatch(setGlobalState({ position: reactPlayerState.played }));
}
};
const handleVolumeChange = (
event: React.ChangeEvent<HTMLInputElement>
): void => {
setPlayerState({
volume: parseFloat(event.target.value)
});
};
const handlePlayPause = (): void => {
if (playerState.playingItem) {
if (playerState.isPlaying) {
emitPlayWish(
playerState.playingItem,
false,
playerState.playingItem.id,
false,
getCurrentPosition()
);
} else {
emitPlayWish(
playerState.playingItem,
true,
null,
false,
getCurrentPosition()
);
}
}
};
const handleSeekMouseDown = (): void => {
setPlayerState({ isSeeking: true });
};
const handleSeekChange = (
event: React.ChangeEvent<HTMLInputElement>
): void => {
if (reactPlayer) {
dispatch(
setGlobalState({ position: parseFloat(event.target.value) })
);
}
};
const handleSeekMouseUp = (
event: React.MouseEvent<HTMLInputElement, MouseEvent>
): void => {
if (playerState.playingItem) {
emitPlayWish(
playerState.playingItem,
playerState.isPlaying,
null,
false,
parseFloat((event.target as HTMLInputElement).value)
);
}
};
const handleFullScreen = async (): Promise<void> => {
if (!playerState.isFullScreen) {
await screenfull.request();
} else {
await screenfull.exit();
}
};
const handleReady = (reactPlayer: ReactPlayer): void => {
setReactPlayer(reactPlayer);
};
const handleBufferEnd = (): void => {
setPlayerState({ isBuffering: false });
};
const handleEnd = async (): Promise<void> => {
if (party && playerState && socket) {
// 1. Emit playWish for next item in playlist
if (party.items.length > playerState.playlistIndex + 1) {
emitPlayWish(
party.items[playerState.playlistIndex + 1],
playerState.isPlaying,
playerState.playingItem ? playerState.playingItem.id : null,
true,
0,
true
);
} else {
emitPlayWish(
party.items[0],
false,
playerState.playingItem ? playerState.playingItem.id : null,
true,
0,
true
);
setPlayerState({
isPlaying: false
});
}
// 2. Update party meta data: Mark item as played && emit party update order
if (playerState.playingItem) {
try {
const response = await Axios.put(
'/api/partyMetadata',
{
partyId: party.id,
metadata: {
...party.metadata,
played: {
...party.metadata.played,
[playerState.playingItem.id]: true
}
}
},
axiosConfig()
);
if (response.data.success) {
socket.emit('partyUpdate', { partyId: party.id });
} else {
dispatch(
setGlobalState({
errorMessage: t(
`apiResponseMessages.${response.data.msg}`
)
})
);
}
} catch (error) {
dispatch(
setGlobalState({
errorMessage: t('errors.metadataUpdateError')
})
);
}
}
}
};
// UI Event handlers
// UI movement detection
const setUiVisible = useCallback(
(visible: boolean): void => {
dispatch(setGlobalState({ uiVisible: visible }));
},
[dispatch]
);
// Prevent UI from hiding when mouse moves
const handleMouseMovementOverUi = (): void => {
if (
Date.now() >
playerTimeoutStateRef.current.uiTimeoutTimestamp +
uiTimeoutIntervalResolution
) {
setUiVisible(true);
if (playerTimeoutStateRef.current.uiTimeoutId) {
clearTimeout(playerTimeoutStateRef.current.uiTimeoutId);
}
setPlayerTimeoutState({
uiTimeoutId: setTimeout(() => {
setUiVisible(false);
}, playerTimeoutStateRef.current.uiTimeoutDelay),
uiTimeoutTimestamp: Date.now()
});
}
};
// Prevent UI from hiding on certain actions in subcomponents
const freezeUiVisible = useCallback(
(freeze: boolean): void => {
const currentDelay = freeze
? uiTimeoutLongDelay
: uiTimeoutShortDelay;
if (playerTimeoutStateRef.current.uiTimeoutId) {
clearTimeout(playerTimeoutStateRef.current.uiTimeoutId);
}
const newTimeoutId = setTimeout(() => {
setUiVisible(false);
}, currentDelay);
setPlayerTimeoutState({
uiTimeoutId: newTimeoutId,
uiTimeoutDelay: currentDelay,
uiTimeoutTimestamp: Date.now()
});
},
[setUiVisible]
);
return (
<div
onMouseMove={(): void => {
handleMouseMovementOverUi();
}}
className={'bg-transparent' + (uiVisible ? '' : ' noCursor')}
>
<div
onMouseDown={handlePlayPause}
className={
'flex w-full h-full reactPlayer bg-transparent' +
(webRtcGlobalState.isFullscreen ? ' z-40' : '')
}
>
<MediaPlayerOverlay
playerState={playerState}
playerTimeoutState={playerTimeoutState}
setPlayerTimeoutState={(
playerTimeoutState: PlayerTimeoutStateActionProperties
): void => setPlayerTimeoutState(playerTimeoutState)}
actionMessageDelay={actionMessageDelay}
></MediaPlayerOverlay>
<div className="flex w-full h-full pointer-events-none">
<ReactPlayer
config={{
youtube: { playerVars: { disablekb: 1 } }
}}
url={playerState.sourceUrl}
playing={playerState.isPlaying}
playsinline={true}
volume={playerState.volume}
progressInterval={100}
onBufferEnd={handleBufferEnd}
onDuration={handleDuration}
onProgress={handleProgress}
onReady={handleReady}
onEnded={handleEnd}
width="100%"
height="100%"
></ReactPlayer>
</div>
</div>
{party && user && (
<CommunicationContainer
socket={socket}
partyId={party.id}
webRtcIds={party.settings.webRtcIds}
ourUserId={user.id}
setPlayerState={setPlayerState}
uiVisible={uiVisible}
freezeUiVisible={freezeUiVisible}
handlePlayPause={handlePlayPause}
></CommunicationContainer>
)}
<MediaMenu
socket={socket}
playerState={playerState}
isPlaying={playerState.isPlaying}
emitPlayWish={emitPlayWish}
setPlayerFocused={(focused: boolean): void =>
setPlayerState({ isFocused: focused })
}
freezeUiVisible={freezeUiVisible}
></MediaMenu>
<BottomBar
playerState={playerState}
handlePlayPause={handlePlayPause}
handleSeekMouseDown={handleSeekMouseDown}
handleSeekChange={handleSeekChange}
handleSeekMouseUp={handleSeekMouseUp}
handleVolumeChange={handleVolumeChange}
handleFullScreen={handleFullScreen}
></BottomBar>
<TopBar socket={socket}></TopBar>
<AlertContainer
playerState={playerState}
emitPlayWish={emitPlayWish}
hasLastPosition={hasLastPosition}
setHasLastPosition={setHasLastPosition}
></AlertContainer>
</div>
);
}
Example #13
Source File: BottomBar.tsx From sync-party with GNU General Public License v3.0 | 4 votes |
export default function BottomBar({
playerState,
handlePlayPause,
handleSeekMouseDown,
handleSeekChange,
handleSeekMouseUp,
handleVolumeChange,
handleFullScreen
}: Props): JSX.Element {
const { t } = useTranslation();
const party = useSelector((state: RootAppState) => state.globalState.party);
const playingItem = useSelector(
(state: RootAppState) => state.globalState.playingItem
);
const syncStatus = useSelector(
(state: RootAppState) => state.globalState.syncStatus
);
const memberStatus = useSelector(
(state: RootAppState) => state.globalState.memberStatus
);
const uiVisible = useSelector(
(state: RootAppState) => state.globalState.uiVisible
);
const position = useSelector(
(state: RootAppState) => state.globalState.position
);
return (
<div className="flex flex-col">
<div className="w-full absolute bottom-0 pb-12 z-40 align-bottom flex flex-row justify-end">
<SyncStatus
party={party}
playerState={playerState}
syncStatus={syncStatus}
memberStatus={memberStatus}
uiVisible={uiVisible}
></SyncStatus>
</div>
<div
className={
'flex flex-row px-1 w-full absolute bottom-0 left-0 backgroundShade z-50' +
(playingItem && uiVisible ? '' : ' hidden')
}
>
<ButtonIcon
title={playerState.isPlaying ? 'Pause' : 'Play'}
icon={
playerState.isPlaying ? (
<FontAwesomeIcon
size="lg"
icon={faPause}
></FontAwesomeIcon>
) : (
<FontAwesomeIcon
size="lg"
icon={faPlay}
></FontAwesomeIcon>
)
}
onClick={handlePlayPause}
className="p-2"
></ButtonIcon>
{playingItem && (
<RangeSlider
ariaLabel="Seek bar"
value={position}
onMouseDown={handleSeekMouseDown}
onChange={handleSeekChange}
onMouseUp={handleSeekMouseUp}
className={
'slider' +
(playerState.duration !== Infinity // Streams, e.g. online radio
? ''
: ' invisible')
}
></RangeSlider>
)}
<div className="mx-2 my-auto">
<Duration
className={
playerState.duration !== Infinity
? ''
: ' invisible'
}
seconds={playerState.duration * position}
></Duration>
</div>
<RangeSlider
ariaLabel="Volume Slider"
className={'slider slider-alt slider-small mr-2'}
onChange={handleVolumeChange}
value={playerState.volume}
width={'w-24'}
></RangeSlider>
<div className="mr-2 my-auto">
<ButtonIcon
onClick={(
event: React.MouseEvent<HTMLInputElement>
): void => {
handleFullScreen(event);
}}
title={t('common.fullscreen')}
icon={
<FontAwesomeIcon
className="text-gray-200 hover:text-purple-500"
icon={
playerState.isFullScreen
? faCompress
: faExpand
}
size="lg"
></FontAwesomeIcon>
}
></ButtonIcon>
</div>
</div>
</div>
);
}