Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[DEPRECATED] Feature: Adds integration of topik/youtube-music-obs-widget as plugin #1120

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from

Conversation

topik
Copy link

@topik topik commented Apr 19, 2023

Hello,

I got a request to add a plugin for my obs widget so here it is.

Plugin makes a simple http server and give a info about current song in JSON. The JSON structure is the same as in other YT music software that is being used so the obs widget is compatible.

Feel free to rename the plugin.

Thanks

@Araxeus
Copy link
Collaborator

Araxeus commented Apr 25, 2023

This looks extremely similar to https://github.com/th-ch/youtube-music/blob/master/plugins/tuna-obs/back.js

Could you give a quick explanation why a new plugin is needed?

@topik
Copy link
Author

topik commented Apr 25, 2023

I took some inspiration from tuna-obs because I'm not a Node.js developer. Tuna-obs sends requests to the OBS plugin itself, which means you have to install the OBS (and the OBS plugin) for the whole thing to work.

However, my plugin works the opposite way. It creates a webserver and serves JSON data about the current song, so you don't need to install anything. Just open the plugin's webpage and it will work. You can add the webpage as a source to OBS.

Additionally, my plugin has a different design.

Copy link
Contributor

@ArjixWasTaken ArjixWasTaken left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PS: You should use a formatter on your code :^)

plugins/obs-widget/back.js Outdated Show resolved Hide resolved
plugins/obs-widget/back.js Outdated Show resolved Hide resolved
@topik
Copy link
Author

topik commented Jul 21, 2023

PS: You should use a formatter on your code :^)

You are absolutely right, no idea why i didn't do that. I would do it in any other language :| ... Also thanks for pointing out wrong variable type.

@JellyBrick JellyBrick added enhancement New feature or request need rebase This plugin need rebase labels Oct 7, 2023
@JellyBrick JellyBrick changed the title Feature: Adds integration of topik/youtube-music-obs-widget as plugin [DEPRECATED] Feature: Adds integration of topik/youtube-music-obs-widget as plugin Dec 27, 2023
@JellyBrick
Copy link
Collaborator

@topik
Could you rework this PR? The entire plugin system has been reworked.

@JellyBrick
Copy link
Collaborator

If you are still interested, this may be helpful: https://github.com/th-ch/youtube-music/pull/2723/files

@ArjixWasTaken
Copy link
Contributor

ArjixWasTaken commented Jan 6, 2025

Nah, a rewrite to work with the new codebase wouldn't be enough.
I did just that

import { createPlugin } from '@/utils';
import { t } from '@/i18n';

import { Hono } from 'hono';
import { serve, ServerType } from '@hono/node-server';
import registerCallback, { SongInfo } from '@/providers/song-info';

let server: ServerType | null = null;
const port = 9863;

export default createPlugin({
  name: () => t('plugins.obs-widget.name'),
  description: () => t('plugins.obs-widget.description'),

  authors: ['ArjixWasTaken'],
  restartNeeded: false,

  backend: {
    start() {
      let songInfo: SongInfo | null = null;
      registerCallback((info) => {
        songInfo = info;
      });

      const app = new Hono();

      app.get('/query', async (ctx) => {
        ctx.res.headers.set('Access-Control-Allow-Origin', '*');
        return ctx.json({
          player: {
            hasSong: !!songInfo,
            isPaused: songInfo?.isPaused ?? false,
            seekbarCurrentPosition: songInfo?.elapsedSeconds ?? 0,
          },
          track: {
            author: songInfo ? [songInfo?.artist] : [],
            title: songInfo?.title ?? '',
            album: songInfo?.album ?? '',
            cover: songInfo?.imageSrc ?? '',
            duration: songInfo?.songDuration ?? 0,
          },
        });
      });

      server = serve({ fetch: app.fetch, port });
    },
    stop() {
      server?.close();
      server = null;
    },
  },
});

but when I saw the client code, it doesn't even use the /query endpoint, it uses /api/v1/* stuff specific to ytmd

it is trying to authenticate, and if it fails it will show annoying alerts to the user
image

@ArjixWasTaken
Copy link
Contributor

One solution would be to fake compatibility with the ytmd API, and only implement the endpoints used, but do we want that?

@ArjixWasTaken
Copy link
Contributor

Faking ytmd compatibility requires socket.io as a dependency, and I am not a fan of that.
But hey, it can be done

image

@ArjixWasTaken
Copy link
Contributor

ArjixWasTaken commented Jan 6, 2025

I will not be making a PR on this, because I do not really want it merged.
But I'll be leaving my code here

import { createPlugin } from '@/utils';
import { t } from '@/i18n';

import { Hono } from 'hono';

import { logger } from 'hono/logger';
import { serve } from '@hono/node-server';
import { Server as SocketIO } from 'socket.io';

import registerCallback, { SongInfo } from '@/providers/song-info';

let server: ServerType | null = null;
const port = 9863;

export default createPlugin({
  name: () => t('plugins.obs-widget.name'),
  description: () => t('plugins.obs-widget.description'),

  authors: ['ArjixWasTaken'],
  restartNeeded: false,

  backend: {
    start() {
      let songInfo: SongInfo | null = null;
      const app = new Hono().use(logger());

      app.use(async (ctx, next) => {
        await next();
        ctx.res.headers.set('Access-Control-Allow-Origin', '*');
      });

      app.options('/api/v1/*', async (ctx) => {
        ctx.res.headers.set('Access-Control-Allow-Methods', 'GET, POST');
        ctx.res.headers.set('Access-Control-Allow-Headers', 'Content-Type');
        return ctx.text('');
      });

      app.post('/api/v1/auth/requestcode', async (ctx) => {
        return ctx.json({ statusCode: 200, code: 'lmao' });
      });

      app.post('/api/v1/auth/request', async (ctx) => {
        return ctx.json({ token: 'lmao' });
      });

      app.get('/api/v1/state', async (ctx) => {
        return ctx.json({
          player: {
            trackState: songInfo ? (songInfo.isPaused ? 0 : 1) : -1,
            volume: 1,
          },
          video: {
            author: songInfo?.artist ?? '',
            channelId: '',
            title: songInfo?.title ?? '',
            album: songInfo?.album,
            albumId: null,
            likeStatus: -1,
            thumbnails: [
              {
                url: songInfo?.imageSrc,
                width: 100,
                height: 100,
              },
            ],
            durationSeconds: songInfo?.songDuration ?? 0,
            id: songInfo?.videoId,
          },
        });
      });

      server = serve({ fetch: app.fetch, port });
      const ws = new SocketIO(server, {
        serveClient: false,
        transports: ['websocket'],
      });

      ws.of('/api/v1/realtime').on('connection', (socket) => {
        console.log('Connected to OBS');
      });

      registerCallback((info) => {
        songInfo = info;

        ws.of('/api/v1/realtime').emit('state-update', {
          player: {
            trackState: songInfo ? (songInfo.isPaused ? 0 : 1) : -1,
            videoProgress: songInfo.elapsedSeconds,
            volume: 1,
          },
          video: {
            author: songInfo?.artist ?? '',
            channelId: '',
            title: songInfo?.title ?? '',
            album: songInfo?.album,
            albumId: null,
            likeStatus: -1,
            thumbnails: [
              {
                url: songInfo?.imageSrc,
                width: 100,
                height: 100,
              },
            ],
            durationSeconds: songInfo?.songDuration ?? 0,
            id: songInfo?.videoId,
          },
        });
      });
    },
    stop() {
      server?.close();
      server = null;
    },
  },
});

Honestly, I'd suggest we make our own realtime websocket using hono's websocket helper.


PS: I did try faking socket.io compatibility, but their documentation doesn't explain shit, so I gave up and used their library.

@topik
Copy link
Author

topik commented Jan 6, 2025

If there is an accessible endpoint, I can change the widget so it works with both programs, so there will be no authentication for https://github.com/th-ch/youtube-music/tree/master. The question is, who's gonna make the endpoint and how.

PS: good job 😂

@ArjixWasTaken
Copy link
Contributor

ArjixWasTaken commented Jan 6, 2025

If there is an accessible endpoint, I can change the widget so it works with both programs

we do not currently have websockets, but the plugin api-server does have an endpoint to get the player state as well as the current song, so it would be similar to the /query you used to call in the past

here is the schema of the response, and here is the route

I do see that having websockets would be useful, so enhancing the api-server plugin with it would be an option

@topik
Copy link
Author

topik commented Jan 6, 2025

Then I can just use the plugin that already exists 🙂 I think that would be the easiest solution.

@ArjixWasTaken
Copy link
Contributor

you'll have to deal with authentication there as well 😔, although it is optional iirc

@ArjixWasTaken
Copy link
Contributor

since I'm invested in this topic already, leave it to me, I'll make a PR to your widget

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request need rebase This plugin need rebase
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants