diff --git a/README.md b/README.md index 01a0d06..919917a 100644 --- a/README.md +++ b/README.md @@ -15,8 +15,9 @@ This plugin solves this issue by allowing you to: - Set the hotkeys for opening the YouTube Player and inserting timestamps (my default is cmnd-shift-y and cmnd-y, respectively) - Highlight a YouTube url and select either the Ribbon note icon or the Open YouTube Player hotkey - Jot down notes and anytime you want to insert a timestamp, press the registered hotkey -- Toggle pause the player by using hotkey (my default is option space) -- Close the player by right-clicking the icon above the YouTube player and selecting close +- Toggle pausing/playing the video by using hotkey (my default is option space) +- Open videos at the timestamp you left off on (this is reset if plugin is disabled) +- Close the player by right-clicking the icon above the YouTube player and selecting close ## Demo diff --git a/YTContainer.tsx b/YTContainer.tsx index cd8bd75..1129f97 100644 --- a/YTContainer.tsx +++ b/YTContainer.tsx @@ -2,20 +2,28 @@ import * as React from "react"; import { useState } from 'react'; import YouTube, { YouTubeProps, YouTubePlayer } from 'react-youtube'; -export const YTContainer = ({ url, setupPlayer }: { url: string, setupPlayer: (yt: YouTubePlayer) => void }): JSX.Element => { + +export interface YTContainerProps { + url: string; + setupPlayer: (yt: YouTubePlayer) => void; + start: number +} + +export const YTContainer = ({ url, setupPlayer, start }: YTContainerProps): JSX.Element => { + const [options] = useState({ height: '410', width: '100%', playerVars: { // https://developers.google.com/youtube/player_parameters autoplay: 1, + start: start, } }); - const onPlayerReady: YouTubeProps['onReady'] = (event) => { // access to player in all event handlers via event.target - setupPlayer(event.target) + setupPlayer(event.target); } return ( diff --git a/main.ts b/main.ts index b13e7f0..2d9dbc4 100644 --- a/main.ts +++ b/main.ts @@ -5,16 +5,19 @@ import { YouTubePlayer } from 'react-youtube'; interface YoutubeTimestampPluginSettings { mySetting: string; player: YouTubePlayer; + urlStartTimeMap: Map; + currURL: string; } const DEFAULT_SETTINGS: YoutubeTimestampPluginSettings = { mySetting: "", - player: undefined + player: undefined, + urlStartTimeMap: new Map(), + currURL: undefined, } export default class YoutubeTimestampPlugin extends Plugin { settings: YoutubeTimestampPluginSettings; - // Helper function to validate url and activate view validateURL = (url: string) => { const regExp = /^.*((youtu.be\/)|(v\/)|(\/u\/\w\/)|(embed\/)|(watch\?))\??v?=?([^#&?]*).*/; @@ -75,7 +78,7 @@ export default class YoutubeTimestampPlugin extends Plugin { id: 'trigger-youtube-player', name: 'Open Youtube Player (copy youtube url and use hotkey)', editorCallback: (editor: Editor, view: MarkdownView) => { - // Get selected text and match against youtube url to convert link to youtube video id + // Get selected text and match against youtube url to convert link to youtube video id => also triggers activateView in validateURL const url = editor.getSelection().trim(); editor.replaceSelection(editor.getSelection() + "\n" + this.validateURL(url)); editor.setCursor(editor.getCursor().line + 1) @@ -126,8 +129,15 @@ export default class YoutubeTimestampPlugin extends Plugin { }); } - onunload() { + async onunload() { + if (this.settings.player) { + this.settings.player.destroy(); + } + + this.settings.player = null; + this.settings.currURL = null; this.app.workspace.detachLeavesOfType(YOUTUBE_VIEW); + await this.saveSettings(); } // This is called when a valid url is found => it activates the View which loads the React view @@ -144,6 +154,7 @@ export default class YoutubeTimestampPlugin extends Plugin { ); + this.settings.currURL = url; // This triggers the React component to be loaded this.app.workspace.getLeavesOfType(YOUTUBE_VIEW).forEach(async (leaf) => { if (leaf.view instanceof YoutubeView) { @@ -151,14 +162,28 @@ export default class YoutubeTimestampPlugin extends Plugin { const setupPlayer = (yt: YouTubePlayer) => { this.settings.player = yt; } - leaf.setEphemeralState({ url, setupPlayer }); + + const saveTimeOnUnload = async () => { + if (this.settings.player) { + this.settings.urlStartTimeMap.set(this.settings.currURL, this.settings.player.getCurrentTime().toFixed(0)); + } + await this.saveSettings(); + } + + // create a new YoutubeView instance, sets up state/unload functionality, and passes in a start time if available else 0 + leaf.setEphemeralState({ url, setupPlayer, saveTimeOnUnload, start: ~~this.settings.urlStartTimeMap.get(url) }); + await this.saveSettings(); } }); } async loadSettings() { - this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData()); + // Fix for a weird bug that turns default map into a normal object when loaded + const data = await this.loadData() + const map = new Map(Object.keys(data.urlStartTimeMap).map(k => [k, data.urlStartTimeMap[k]])) + + this.settings = { ...DEFAULT_SETTINGS, ...data, urlStartTimeMap: map }; } diff --git a/manifest.json b/manifest.json index 5d9b413..61044f9 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "id": "obsidian-youtube-timestamp-notes", "name": "YouTube Timestamp Notes", - "version": "1.0.3", + "version": "1.0.4", "minAppVersion": "0.12.0", "description": "This plugin allows side-by-side notetaking with YouTube videos. Annotate your notes with timestamps to directly control the video and remember where each note comes from.", "author": "Julian Grunauer", diff --git a/view/YoutubeView.tsx b/view/YoutubeView.tsx index cab287f..14b5d55 100644 --- a/view/YoutubeView.tsx +++ b/view/YoutubeView.tsx @@ -1,14 +1,23 @@ import { ItemView, WorkspaceLeaf } from 'obsidian'; import * as React from "react"; import * as ReactDOM from "react-dom"; -import { YTContainer } from "../YTContainer" -import { YouTubePlayer } from 'react-youtube'; +import { createRoot, Root } from 'react-dom/client'; + +import { YTContainer, YTContainerProps } from "../YTContainer" + +export interface YTViewProps extends YTContainerProps { + saveTimeOnUnload: () => void; +} export const YOUTUBE_VIEW = "example-view"; export class YoutubeView extends ItemView { component: ReactDOM.Renderer + saveTimeOnUnload: () => void + root: Root constructor(leaf: WorkspaceLeaf) { super(leaf); + this.saveTimeOnUnload = () => { }; + this.root = createRoot(this.containerEl.children[1]) } getViewType() { @@ -19,14 +28,13 @@ export class YoutubeView extends ItemView { return "Example view"; } - setEphemeralState({ url, setupPlayer }: { url: string, setupPlayer: (yt: YouTubePlayer) => void }) { - ReactDOM.render( - // @ts-ignore - // - , - // , - this.containerEl.children[1] - ); + setEphemeralState({ url, setupPlayer, saveTimeOnUnload, start }: YTViewProps) { + + // Allows view to save the playback time in the setting state when the view is closed + this.saveTimeOnUnload = saveTimeOnUnload; + + // Create a root element for the view to render into + this.root.render(); } async onOpen() { @@ -34,6 +42,8 @@ export class YoutubeView extends ItemView { } async onClose() { + if (this.saveTimeOnUnload) await this.saveTimeOnUnload(); + this.root.unmount() ReactDOM.unmountComponentAtNode(this.containerEl.children[1]); } }