From daa0c057627165942e87b10ef76126d5592d73eb Mon Sep 17 00:00:00 2001 From: Jared Pereira Date: Thu, 9 Apr 2026 22:55:01 -0400 Subject: [PATCH] add bluesky video embed playback --- .../Blocks/BlueskyPostBlock/BlueskyEmbed.tsx | 24 ++-- .../BlueskyPostBlock/BlueskyVideoPlayer.tsx | 122 ++++++++++++++++++ package-lock.json | 7 + package.json | 1 + 4 files changed, 138 insertions(+), 16 deletions(-) create mode 100644 components/Blocks/BlueskyPostBlock/BlueskyVideoPlayer.tsx diff --git a/components/Blocks/BlueskyPostBlock/BlueskyEmbed.tsx b/components/Blocks/BlueskyPostBlock/BlueskyEmbed.tsx index a55c8092..e9bc72f8 100644 --- a/components/Blocks/BlueskyPostBlock/BlueskyEmbed.tsx +++ b/components/Blocks/BlueskyPostBlock/BlueskyEmbed.tsx @@ -15,6 +15,7 @@ import { OpenPage, openPage, } from "app/lish/[did]/[publication]/[rkey]/PostPages"; +import { BlueskyVideoPlayer } from "./BlueskyVideoPlayer"; export const BlueskyEmbed = (props: { embed: Exclude; @@ -131,22 +132,13 @@ export const BlueskyEmbed = (props: { ? videoEmbed.aspectRatio.width / videoEmbed.aspectRatio.height : 16 / 9; return ( -
- { -
-
- -
-
+ ); case AppBskyEmbedRecord.isView(props.embed): let recordEmbed = props.embed; diff --git a/components/Blocks/BlueskyPostBlock/BlueskyVideoPlayer.tsx b/components/Blocks/BlueskyPostBlock/BlueskyVideoPlayer.tsx new file mode 100644 index 00000000..fd22bb4d --- /dev/null +++ b/components/Blocks/BlueskyPostBlock/BlueskyVideoPlayer.tsx @@ -0,0 +1,122 @@ +import { useEffect, useRef, useState } from "react"; + +type Props = { + playlist: string; + thumbnail?: string; + alt?: string; + aspectRatio: number; + className?: string; +}; + +export const BlueskyVideoPlayer = (props: Props) => { + const [playing, setPlaying] = useState(false); + const [loading, setLoading] = useState(false); + const videoRef = useRef(null); + const hlsRef = useRef<{ destroy: () => void } | null>(null); + + useEffect(() => { + if (!playing) return; + const video = videoRef.current; + if (!video) return; + + let cancelled = false; + + // Safari (and some iOS browsers) support HLS natively. + if (video.canPlayType("application/vnd.apple.mpegurl")) { + video.src = props.playlist; + video.play().catch(() => {}); + return () => { + cancelled = true; + video.removeAttribute("src"); + video.load(); + }; + } + + setLoading(true); + import("hls.js") + .then(({ default: Hls }) => { + if (cancelled) return; + if (!Hls.isSupported()) { + // Last-resort fallback: try the native element anyway. + video.src = props.playlist; + video.play().catch(() => {}); + setLoading(false); + return; + } + const hls = new Hls(); + hlsRef.current = hls; + hls.loadSource(props.playlist); + hls.attachMedia(video); + hls.on(Hls.Events.MANIFEST_PARSED, () => { + if (cancelled) return; + setLoading(false); + video.play().catch(() => {}); + }); + }) + .catch(() => { + if (cancelled) return; + setLoading(false); + }); + + return () => { + cancelled = true; + if (hlsRef.current) { + hlsRef.current.destroy(); + hlsRef.current = null; + } + }; + }, [playing, props.playlist]); + + return ( +
+ {!playing && props.thumbnail && ( + {props.alt + )} + {playing && ( +
+ ); +}; diff --git a/package-lock.json b/package-lock.json index 04c59703..398787ca 100644 --- a/package-lock.json +++ b/package-lock.json @@ -46,6 +46,7 @@ "drizzle-orm": "^0.30.10", "feed": "^5.1.0", "fractional-indexing": "^3.2.0", + "hls.js": "^1.6.15", "hono": "^4.7.11", "immer": "^10.2.0", "inngest": "^3.52.6", @@ -13359,6 +13360,12 @@ "hermes-estree": "0.25.1" } }, + "node_modules/hls.js": { + "version": "1.6.15", + "resolved": "https://registry.npmjs.org/hls.js/-/hls.js-1.6.15.tgz", + "integrity": "sha512-E3a5VwgXimGHwpRGV+WxRTKeSp2DW5DI5MWv34ulL3t5UNmyJWCQ1KmLEHbYzcfThfXG8amBL+fCYPneGHC4VA==", + "license": "Apache-2.0" + }, "node_modules/hono": { "version": "4.7.11", "resolved": "https://registry.npmjs.org/hono/-/hono-4.7.11.tgz", diff --git a/package.json b/package.json index 45aa58f0..80f5c350 100644 --- a/package.json +++ b/package.json @@ -57,6 +57,7 @@ "drizzle-orm": "^0.30.10", "feed": "^5.1.0", "fractional-indexing": "^3.2.0", + "hls.js": "^1.6.15", "hono": "^4.7.11", "immer": "^10.2.0", "inngest": "^3.52.6", -- 2.51.2