Skip to content

Commit

Permalink
Added saving playback time to video, updated to new React rendering, …
Browse files Browse the repository at this point in the history
…and added pause
  • Loading branch information
juliang22 committed May 7, 2022
1 parent 82804c8 commit 452f050
Show file tree
Hide file tree
Showing 5 changed files with 66 additions and 22 deletions.
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
14 changes: 11 additions & 3 deletions YTContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<YouTubeProps['opts']>({
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 (
Expand Down
37 changes: 31 additions & 6 deletions main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,19 @@ import { YouTubePlayer } from 'react-youtube';
interface YoutubeTimestampPluginSettings {
mySetting: string;
player: YouTubePlayer;
urlStartTimeMap: Map<string, number>;
currURL: string;
}

const DEFAULT_SETTINGS: YoutubeTimestampPluginSettings = {
mySetting: "",
player: undefined
player: undefined,
urlStartTimeMap: new Map<string, number>(),
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?=?([^#&?]*).*/;
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand All @@ -144,21 +154,36 @@ 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) {

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 };
}


Expand Down
2 changes: 1 addition & 1 deletion manifest.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
30 changes: 20 additions & 10 deletions view/YoutubeView.tsx
Original file line number Diff line number Diff line change
@@ -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() {
Expand All @@ -19,21 +28,22 @@ export class YoutubeView extends ItemView {
return "Example view";
}

setEphemeralState({ url, setupPlayer }: { url: string, setupPlayer: (yt: YouTubePlayer) => void }) {
ReactDOM.render(
// @ts-ignore
// <AppContext.Provider value={this.app}>
<YTContainer url={url} setupPlayer={setupPlayer} />,
// </AppContext.Provider>,
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(<YTContainer url={url} setupPlayer={setupPlayer} start={start} />);
}

async onOpen() {

}

async onClose() {
if (this.saveTimeOnUnload) await this.saveTimeOnUnload();
this.root.unmount()
ReactDOM.unmountComponentAtNode(this.containerEl.children[1]);
}
}
Expand Down

0 comments on commit 452f050

Please sign in to comment.