CSSやp5.jsを学ぶようになってからYouTubeを見ながら手を動かす….
といったことが増えてきました。
以前の記事でもオススメ動画を紹介したものの
「記事内に埋め込んで再生できたらわざわざリンク飛んで見に行かなくても済むよな…」
と思うように。。。。
まだ本家でサポートされていないblockを
ブログに取り込んだ経験なんぞない私が
調べに調べて
弱音を吐きながらも
サンプルを動かしたり
そこから使えそうな部分を移植したり
エラーと格闘したりしながら
学べたことを解説していこうと思います。
YouTubeをNotion上で再生するにはVideo embedのブロックにリンクを入れますよね….。
👇
easy-notion-blogで既にサポートされているblockで
似た雰囲気のblockをNotion上で確認してみます。
なんとな〜く
あたりが似てるのかな〜といった見通しが立ってきました。
以下、3つの探り方で絞っていきます。
まずは、怪しそうな4つのblockを実際にNotion上で表示をさせて見比べてみます。
怪しい4blockを見比べてみて、
リンクを入れて表示させるか否かの部分でCode blockは違うなということが分かりました。
残るは3blockです。
YouTubeをNotion上で再生できるVideo embedを表示すると…
こんな感じになります。
Notion上で再生できるので、YouTubeを使っているような雰囲気になれます。
これに近いblockに狙いを定めたいのです。
怪しい3blockでは
リンクを埋め込んだ後Notion上ではどのように表示されているのかを見比べます。
〇〇○をここで使っているかのような雰囲気。
Tweet blockがダントツです。
実際にNotionからblockの情報を取得するには
Notion integration ドキュメントに明記されているJSONを参照しながらになります。
https://developers.notion.com/reference/block
Video blockのJSONは….
https://developers.notion.com/reference/block#video-blocks
File Objectを含むようです。
先程表示のされ方が似ていたTweet blockを見てみると….
Notionアプリは、サードパーティのサービスであるEmbedlyを使用して、URLで指定されたembedのメタデータを検証して要求します。これは、NotionがURL情報の非同期リクエストを開始し、完了までに数秒またはそれ以上かかる可能性があり、Embedlyからの応答を受け取った後、UIでメタデータを含むブロックを更新できるため、Webアプリでうまく機能します。APIはUIよりも高速に返す必要があり、またEmbedlyからの応答によってブロックの種類が実際に変更される可能性があるため、APIで埋め込みブロックを作成する際にはEmbedlyを呼び出さないことにしました。これは、レスポンスにあるブロックがリクエストで送られたブロックと一致しないため、遅くなり、混乱を招く可能性があります。
その結果、API経由で作成されたエンベッドブロックは、Notionアプリで作成された対応するブロックとまったく同じようには見えないかもしれません。
なんか難しそうなことを言っています…..😵
URLだけでいいようです。
https://developers.notion.com/reference/block#bookmark-blocks
Tweet blockにあったEmbed blockに似ています。
https://developers.notion.com/reference/block#bookmark-blocks
JSON構造的にはImage Blockが似ているなということに気づけました⭐
リンクの埋め込み | 表示のされ方 | JSON構造 | |
---|---|---|---|
Image | ⭕ | ❎ | ⭕ |
Web bookmark | ⭕ | ❎ | ❎ |
Code | ❎ | - | - |
Tweet | ⭕ | ⭕ | ❎ |
ImageとTweetの2blockにアンテナを張りながら実装していけば良さそうです。
Twetterのような表示はどうやって実装されているのか…..
本家のコードを確認してみます。
import { TwitterTweetEmbed } from 'react-twitter-embed'
import styles from '../../styles/notion-block.module.css'
const TweetEmbed = ({ url }) => {
let matched
try {
matched = new URL(url).pathname.match(/\/(\d+)$/)
} catch (error) {
console.log(error)
return null
}
if (!matched) {
return null
}
return (
<div className={styles.tweetEmbed}>
<TwitterTweetEmbed tweetId={matched[1]} />
</div>
)
}
export default TweetEmbed
react-twitter-embed
っていうライブラリのおかげ!?react-twitter-embedの本家リポジトリへ行ってみます。
READMEにはデモページへのリンクも公開されている….
https://saurabhnemade.github.io/react-twitter-embed/
YouTubeを埋め込むライブラリはどのようなものがあるのか……
検索画面で普通に【github react youtube 】と検索すると….
一番上のリポジトリをのぞいてみます。
https://github.com/tjallingt/react-youtube
Forkの件数やStarの数が多いので評判いいのかな〜というのが伝わってきます。
commitも最近まで行われているのでメンテナンスもされてるのかな〜といった安心感。
react-tweet-embedのようなデモページは無いので
code sandboxの力を借りて
セルフデモをしてみようと思います。
評判はいいのに実装した後に表示のされ方が残念だったらショックなので、
実際に動かして確認しようと思います。
画面右上のCreate SandboxからTypescriptのテンプレートを選びます。
react-youtubeリポジトリのREADMEにある記述をコピーし…..
Code sandboxに用意したテンプレートに貼り付けます
やいやい言われるので素直に受け入れて調整します。
調整したこと:
これでデモページを自分で用意することができました\(^o^)/
return にあるvideoIDを変更すると任意の動画を表示できるかを確認してみます。
👇
\(^o^)/
基本的な流れはBlog Learn06で行った部分と関連しています。
https://herohoro.com/blog/blog-learn_page-content_paragraph
今回の流れ:
Notionのblock情報を取得するにはJSON構造を把握しないといけません。
公式ドキュメントを参照して見比べた時、
Image blockにそっくりだということが分かっているので
Image blockのコードを参照しながら追記していこうと思います⭐
JSON構造は同じでもFile Object内で使いたい要素がどれくらいあるかによって取得の仕方がImaga blockと変わるので、その部分も整理しながら解説します。
Notion blockのJSON構造に直接向き合って拾ってくれるのがinterfaces.tsです。
export interface Image {
Caption: RichText[]
Type: string
File?: File
External?: External
Width?: number
Height?: number
}
Image blockではFileも取得しているようです。
Captionも必要ないかな….と思うのでカットすることにします。(widthやheightもいらないね)
必要最低限のblock情報となりました。
export interface Video{
Type: string
External?: External
}
client.tsxで使えるようにBlock関数にVideoを追加しておきます。
記事を表示させるpages/blog/[slug].tsxと深い関係があるのがclient.tsです。
[slug].tsxに記述されているgetStaticProps関数では
記事ごとに本文のblockを取得するgetAllBlocksByBlocksId
があります。
これはclinent.tsで記述されています。
interfaces.tsで用意したvideo要素もclient.tsxに追加して….
Image blockの記述を参考にgetAllBlocksByBlocksId
関数にVideoを追加します。
狙いは定まった!!!!!\(^o^)/
client.tsで[slug].tsxから記事毎のblockを取得できるようになりました。
取得した内容を表示できるようにしているのがcomponentsディレクトリです。
どのように表示しているのかを
ざっくり確認するためにnotion-block.tsxを開きます。
[slug].tsxでblock変数として引数に入れられた時に
該当したらこんなコードにして表示してね
って感じの指示をしているのが分かります。
👇末尾のコードでは【該当したらコンポネントしちゃう】っていうのが如実に明記されています。
表示するためにいろいろ処理が必要な場合にはdynamicしてつなげてもいいようです。
見通しが立ったので実装してみます\(^o^)/
やること:
code sandboxでセルフデモしたコードをそのまま使います。
import styles from '../../styles/notion-block.module.css'
import React from 'react';
import YouTube, {YouTubeProps } from 'react-youtube';
const Video=({block})=> {
const onPlayerReady: YouTubeProps['onReady'] = (event) => {
event.target.pauseVideo();
}
const opts: YouTubeProps['opts'] = {
height: '390',
width: '640',
playerVars: {
autoplay: 1,
},
};
return (
<div className={styles.Video}>
<YouTube videoId="V7v3z09m07s" opts={opts} onReady={onPlayerReady} className={styles.youtube}/>
</div>
);
}
export default Video;
このままではわざわざ引数にblockを入れている意味がないのですが、
後から修正すればいいかなって構えることにします。
video.tsxを用意したことでパスをnotion-block.tsxに追加できるようになったので入れます。
notion-block.tsxの末尾にある条件分岐部分も追加します。
つながった\(^o^)/
セルフデモのコードをコピペしただけのvideo.tsxを修正していきます。
やること:
JSON構造が似ているImage blockのnotion-block.tsxの記述の仕方を参照して追加します。
読み込めているのかをひとまず表示させてみることにします。
returnの部分を細工します。
return (
// <div className={styles.Video}>
// <YouTube videoId="V7v3z09m07s" opts={opts} onReady={onPlayerReady} className={styles.youtube}/>
// </div>
<>
<p>
これはNotion blockから取得したYouTubリンクだよ:{url}
</p>
</>
);
}
Notionにvideo blockを埋め込み….
これを表示させると….
成功\(^o^)/
YouTubeリンクは無事取得できていることが分かったので、
今度はそのリンクにあるvideoIDを拾えるようにします。
tweet-embed.tsxではどのように拾っているかを確認してみると….
謎の記号をmatchメソッドに入れているようです…..。
調べたところこれは【正規表現】という新キャラなようです 🦠
ネットで正規表現ネタを調べて
なんとな〜く謎の記号の意味が分かってきました。
YouTubeリンクからIDを拾うには…..
と難しく考えず単純に検索バーに「正規表現 youtube videoID」と入力するといろいろヒットします。
しっくり来ない場合は正規表現を英語Regex
にして検索するとフィットする確率大です。
ドンピシャの情報を発見!!!
https://www.web-dev-qa-db-ja.com/ja/javascript/urlからyoutubeビデオidを取得する方法/1067339059/
この記事によると….
/(?:https?:\/{2})?(?:w{3}\.)?youtu(?:be)?\.(?:com|be)(?:\/watch\?v=|\/)([^\s&]+)/)
といった正規表現で解決するようです。
ポイントごとに解説もあり一人で感動していました。
YouTubeリンクからvideoIDを拾うためにVIDEOSという変数を追加します。
const Video=({block})=> {
const url = block.Video.External.Url
const VIDEOS =url.match(/(?:https?:\/{2})?(?:w{3}\.)?youtu(?:be)?\.(?:com|be)(?:\/watch\?v=|\/)([^\s&]+)/);
//以下同文
このまま表示しても何も変わらないので
console.log
で中身を確認することにします。
const Video=({block})=> {
const url = block.Video.External.Url
const VIDEOS =url.match(/(?:https?:\/{2})?(?:w{3}\.)?youtu(?:be)?\.(?:com|be)(?:\/watch\?v=|\/)([^\s&]+)/);
const onPlayerReady: YouTubeProps['onReady'] = (event) => {
event.target.pauseVideo();
}
const opts: YouTubeProps['opts'] = {
height: '390',
width: '640',
playerVars: {
autoplay: 1,
},
};
console.log(VIDEOS)//👈 追加
return (
//以下同文
するとターミナルにVIDEOSの情報が表示されます。
欲しいのは2つ目の部分。
表示させてみます。
return (
// <div className={styles.Video}>
// <YouTube videoId={videoID} opts={opts} onReady={onPlayerReady} className={styles.youtube}/>
// </div>
<>
<p>
これはNotion blockから取得したYouTubリンクだよ:{url}
videoIDがとれてるかな?{VIDEOS[1]} {/* 👈 追加 */}
</p>
</>
);
}
👇
とれてるじゃーーーーん\(^o^)/
無事videoIDを取得できたので、
本来の記述に戻して様子を見ます。
return (
<div className={styles.Video}>
<YouTube videoId={VIDEOS[1]} opts={opts} onReady={onPlayerReady} className={styles.youtube}/>
</div>
// <>
// <p>
// これはNotion blockから取得したYouTubリンクだよ:{url}<br/>
// videoIDがとれてるかな?{VIDEOS[1]}
// </p>
// </>
);
👇
完成\(^o^)/
再生してみてみて〜〜〜〜〜(*´∀`*)(*´∀`*)♥
本家でサポートされていないblockでも既にあるコードを手がかりにすれば
なんだかんだでセルフサポートできるということが分かりました。
実際1週間くらい格闘する中で身についてきた術があります。
いろいろ追記しないといけないことはあるんだけど、
優先順位を付けて「今これやる!」「ここまでやれたら次これやる!」
みたいな切り替えができるようになってきたという術です。
分からない部分を検索するにしても
キーワードの候補も上げられず右往左往してばかりだった状態から
なんとな〜く候補ワードを入力できるようになってきたかなと思います。
まだまだイメージする力が弱いので
進捗状況を表示して確認する工程は必要だなと感じ、
途中謎の<p>タグの記述やconsole.logをはさみながら進めました 😋
何事も見える化って大切だなと感じたvideo embedの実装でした\(^o^)/
作業中にぶつぶつツイートしたスレッドはこちら(*´ω`*)(*´ω`*)www
本家にPRしてみました。(ドキドキ)
いろいろ仕上げを手伝っていただきながら本家へ正式Mergeを果たしましたとさ。
めでたしめでたし
▼ この記事に興味があったら同じタグから関連記事をのぞいてみてね
RSSリーダーにatomのリンクを登録すると通知が行くよ🐌
https://herohoro.com/atom
やってみてね(*´ω`*)(*´ω`*)
フォロー大歓迎\(^o^)/
フォロー大歓迎\(^o^)/