TEST POST BY EXAMPLE ACCOUNT
まだ手元のパソコンでイベント配信してるんですか?クラウド上でTeamsを利用してOBSで配信した方が楽ですよ。
配信のために高いパソコンや配信機材を買ったり、一人で頑張る必要が無くなります。
また。自宅のネットワーク環境を気にする必要がありません。
これまで手元のパソコンでイベント配信を運用してきて下記のような問題が発生しています。
- 配信するスタッフの確保問題(ほぼ趣味)
- 同等の環境を冗長することが難しい
- そのため当日の交代が出来ない
イベント運用側としては頭の痛い問題です。
要は手離れができないです。
これらを解決する方法としてクラウド上のIaaSを利用しスタッフ共同運用を行えば解決できると考えています。
配信している方の一部には、すでにクラウド上から配信を行っているケースもありました。しかしスタッフ共同で運用する場合以下が問題だと思っていました。
- リモートデスクトップのセッションが切断されると真っ黒な画面が配信がされる問題
- 接続できるセッションが1つに限定されるため結局一人で運用
- 結局Zoomなどを利用していた場合、出力される画面キャプチャを監視しズレ防止が必要
これは現状いろいろ試したのですが解決できませんでした。
そこに登場したMicrosoft TeamsのNDI機能を利用した配信方法が解決してくれました。
https://level69.net/archives/27770
上記の応用編になるためTeasmの利用方法やNDI機能の説明は省きます。リンク先を参考にしてください。
今回は以下の要件を満たすように環境を構築していきたいと思います。
- スピーカーは自宅から参加する
- 運用するスタッフは自宅から参加する
- スタッフは2人以上
- リモートで共同運用する
- リモートデスクトップでセッションが切れても問題ない
全体的な構成は以下です。
import { useRouter } from "next/router" import { Button, Heading, Stack, Text } from "@chakra-ui/react" import HTMLViewer from "components/Viewer/HTMLViewer" import JupyterViewer from "components/Viewer/JupyterViewer" import { useEffect, useMemo, useState } from "react" import { useTranslation } from "react-i18next" import { POST_TYPE_EVENT, POST_TYPE_IPYNB, POST_TYPE_TEXT, } from "constants/post" import { qbmPaidAboutPath } from "utils/url" import { BeatLoader } from "react-spinners" interface PostDetailContentProp { post: Post profile: Profile | null handleAttend: () => Promise<void> } export const PostDetailContent = ({ post, profile, handleAttend, }: PostDetailContentProp) => { const { t } = useTranslation() const router = useRouter() // const [removeMask, setRemoveMask] = useState<boolean>(false) // const [needSubscribe, setNeedSubscribe] = useState<boolean>(true) const [removeMask, setRemoveMask] = useState<boolean | null>(null) const [needSubscribe, setNeedSubscribe] = useState<boolean | null>(null) const [postContent, setPostContent] = useState<string | undefined>( post.content?.slice(0, 50) ) useEffect(() => { setTimeout(() => {}, 1000) setRemoveMask( // 無料記事 !post.subscribers_only || // 有料記事 and (購読者 or 記事作成者) (post.subscribers_only && (post.has_subscription || profile?.user_id === post.created_by_id)) ) }, [post, profile]) // useEffect(() => { // if (removeMask) { // setNeedSubscribe(false) // setPostContent(post.content as string) // } else { // setNeedSubscribe(true) // setPostContent(post.content?.slice(0, 50) ?? "") // } // }, [post, removeMask]) useEffect(() => { if (removeMask) { setNeedSubscribe(false) setPostContent(post.content as string) } }, [post, removeMask]) const isOwner = useMemo(() => { return ( profile && post && post.created_by_id && profile.user_id === post.created_by_id ) }, [post, profile]) const canReadEventInfo = useMemo(() => { return isOwner || (profile && post.is_attendee) }, [post, profile]) const AttendText = () => { return canReadEventInfo ? ( <HTMLViewer value={post.information_for_attendee || ""} id={`information_for_attendee-${post.id}`} /> ) : ( <Text>{t("text.need_attend_to_show")}</Text> ) } const AttendButton = () => { return isOwner ? null : ( <Button onClick={handleAttend}> {post.is_attendee ? t("action.cancel_attend") : t("action.attend")} </Button> ) } const MoveToSubscribePage = () => { return ( <Stack spacing={4}> <Text>{t("text.need_to_pay")}</Text> <Button onClick={() => router.push(qbmPaidAboutPath)}> {t("text.to_apply_page")} </Button> </Stack> ) } const PostTypeContent = ({ postType }: { postType: PostType }) => { switch (postType) { case POST_TYPE_IPYNB: return ( <JupyterViewer data={post.content} user_id={post.created_by_id} /> ) case POST_TYPE_TEXT: return ( <Stack spacing={10}> <HTMLViewer value={postContent} id={post.id} /> {needSubscribe && <MoveToSubscribePage />} </Stack> ) case POST_TYPE_EVENT: return ( <Stack> <Heading as="h2">{t("text.event_description")}</Heading> <HTMLViewer value={post.content as string} id={`content-${post.id}`} /> <Heading as="h2"> {t("text.event_information_for_attendee")} </Heading> <AttendText /> <AttendButton /> </Stack> ) } } return ( <> <PostTypeContent postType={post.post_type} /> </> ) }
Comments