@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 vote down vote up
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 vote down vote up
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 ? <>&nbsp;{title}</> : null}
            </Button>
        );
    }
Example #3
Source File: ItemListedPlayStatus.tsx    From sync-party with GNU General Public License v3.0 6 votes vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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}/>
						&#32;
						Test
					</Button>
					<Button onClick={onUpload}>
						<FontAwesomeIcon icon={faUpload}/>
						&#32;
						Upload
					</Button>
				</Fragment>
			)}
			<FilePicker>
				<FontAwesomeIcon icon={faFolderOpen}/>
				&#32;
				Import<input accept="application/json" type="file" onChange={onLoad}/></FilePicker>
			<Button onClick={onSave}>
				<FontAwesomeIcon icon={faSave}/>
				&#32;
				Export
			</Button>
		</Fragment>
	);
}
Example #11
Source File: index.tsx    From nouns-monorepo with GNU General Public License v3.0 4 votes vote down vote up
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 vote down vote up
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 vote down vote up
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>
    );
}