@@ -71,14 +71,20 @@ export function EmbedOpenButton({ url }: EmbedOpenButtonProps) {
7171}
7272
7373type YoutubeElementProps = {
74- videoId : string ;
74+ videoInfo : YoutubeLink ;
7575 embedData : OEmbed ;
7676} ;
7777
78- export const YoutubeElement = as < 'div' , YoutubeElementProps > ( ( { videoId, embedData } ) => {
79- const thumbnailUrl = `https://i.ytimg.com/vi/${ videoId } /hqdefault.jpg` ;
80- const iframeSrc = `https://www.youtube-nocookie.com/embed/${ encodeURIComponent ( videoId ) } ?autoplay=1` ;
81- const videoUrl = `https://youtube.com/watch?v=${ videoId } ` ;
78+ export const YoutubeElement = as < 'div' , YoutubeElementProps > ( ( { videoInfo, embedData } ) => {
79+ const thumbnailUrl = `https://i.ytimg.com/vi/${ videoInfo . videoId } /hqdefault.jpg` ;
80+
81+ const timestamp = videoInfo . timestamp ? `&start=${ videoInfo . timestamp } ` : '' ;
82+ const playlist = videoInfo . playlist ? `&${ videoInfo . playlist } ` : '' ;
83+
84+ const iframeSrc = `https://www.youtube-nocookie.com/embed/${ encodeURIComponent ( videoInfo . videoId ) } ?autoplay=1${ timestamp } ` ;
85+ const videoUrl = videoInfo . isMusic
86+ ? `https://music.youtube.com/watch?v=${ videoInfo . videoId } ${ timestamp } ${ playlist } `
87+ : `https://youtube.com/watch?v=${ videoInfo . videoId } ${ timestamp } ${ playlist } ` ;
8288
8389 const [ blurHash , setBlurHash ] = useState < string | undefined > ( ) ;
8490
@@ -141,19 +147,56 @@ export const YoutubeElement = as<'div', YoutubeElementProps>(({ videoId, embedDa
141147} ) ;
142148
143149export const youtubeUrl = ( url : string ) =>
144- url . match ( / ( h t t p s : \/ \/ ) ( w w w \. | m \. | ) ( y o u t u b e \. c o m | y o u t u \. b e ) \/ / ) ;
150+ url . match ( / ( h t t p s : \/ \/ ) ( w w w \. | m u s i c \. | m \. | ) ( y o u t u b e \. c o m | y o u t u \. b e ) \/ / ) ;
151+
152+ type YoutubeLink = {
153+ videoId : string ;
154+ timestamp ?: string ;
155+ playlist ?: string ;
156+ isMusic : boolean ;
157+ } ;
158+
159+ function parseYoutubeLink ( url : string ) : YoutubeLink | null {
160+ const urlsplit = url . split ( '/' ) ;
161+ const path = urlsplit [ urlsplit . length - 1 ] ;
162+
163+ let videoId : string | undefined ;
164+ let params : string [ ] ;
165+
166+ if ( url . includes ( 'youtu.be' ) ) {
167+ const split = path . split ( '?' ) ;
168+ [ videoId ] = split ;
169+ params = split [ 1 ] . split ( '&' ) ;
170+ } else {
171+ params = path . split ( '?' ) [ 1 ] . split ( '&' ) ;
172+ videoId = params . find ( ( s ) => s . startsWith ( 'v=' ) , params ) ?. split ( 'v=' ) [ 1 ] ;
173+ }
174+
175+ if ( ! videoId ) return null ;
176+
177+ // playlist is not used for the embed, it can be appended as is
178+ const playlist = params . find ( ( s ) => s . startsWith ( 'list=' ) , params ) ;
179+ const timestamp = params . find ( ( s ) => s . startsWith ( 't=' ) , params ) ?. split ( 't=' ) [ 1 ] ;
180+
181+ return {
182+ videoId,
183+ timestamp,
184+ playlist,
185+ isMusic : url . includes ( 'music.youtube.com' ) ,
186+ } ;
187+ }
145188
146189export const ClientPreview = as < 'div' , { url : string } > ( ( { url, ...props } , ref ) => {
147190 const [ showYoutube ] = useSetting ( settingsAtom , 'clientPreviewYoutube' ) ;
148191
149192 // this component is overly complicated, because it was designed to support more embed types than just youtube
150193 // i'm leaving this mess here to support later expansion
151194 const isYoutube = ! ! youtubeUrl ( url ) ;
152- const videoId = isYoutube ? url . match ( / (?: s h o r t s \/ | w a t c h \? v = | y o u t u \. b e \/ ) ( . { 11 } ) / ) ?. [ 1 ] : null ;
195+ const videoInfo = isYoutube ? parseYoutubeLink ( url ) : null ;
153196
154197 const fetchUrl =
155- isYoutube && videoId
156- ? `https://www.youtube.com/oembed?url=${ encodeURIComponent ( `https://youtube.com/watch?v=${ videoId } ` ) } `
198+ isYoutube && videoInfo
199+ ? `https://www.youtube.com/oembed?url=${ encodeURIComponent ( `https://youtube.com/watch?v=${ videoInfo . videoId } ` ) } `
157200 : url ;
158201
159202 const [ embedStatus , loadEmbed ] = useAsyncCallback (
@@ -168,12 +211,12 @@ export const ClientPreview = as<'div', { url: string }>(({ url, ...props }, ref)
168211
169212 let previewContent ;
170213
171- if ( isYoutube && videoId ) {
214+ if ( isYoutube && videoInfo ) {
172215 if ( showYoutube ) {
173216 if ( embedStatus . status === AsyncStatus . Error ) return null ;
174217
175218 if ( embedStatus . status === AsyncStatus . Success && embedStatus . data ) {
176- previewContent = < YoutubeElement videoId = { videoId } embedData = { embedStatus . data } /> ;
219+ previewContent = < YoutubeElement videoInfo = { videoInfo } embedData = { embedStatus . data } /> ;
177220 } else {
178221 previewContent = (
179222 < Box grow = "Yes" alignItems = "Center" justifyContent = "Center" >
0 commit comments