From: Daniel Karbach Date: Sat, 9 Nov 2024 14:26:39 +0000 (+0100) Subject: add clip player X-Git-Url: https://git.localhorst.tv/?a=commitdiff_plain;h=c17fe69604c629b6fbb847515959441c38a6294f;p=ffmpeg-test.git add clip player --- diff --git a/src/app/Application.h b/src/app/Application.h index b888ba5..1d7d6b6 100644 --- a/src/app/Application.h +++ b/src/app/Application.h @@ -6,6 +6,7 @@ #include #include "ChannelInfo.h" +#include "ClipPlayer.h" #include "Config.h" #include "DrawingGame.h" #include "Mixer.h" @@ -128,7 +129,6 @@ private: ChannelInfo &info = state.GetChannelInfo(channel_id); info.Update(channel); } - ShoutoutChannel(33); } void HandlePusherChannel(const Json::Value &json) { @@ -153,7 +153,7 @@ private: if (went_live) { // channel went live std::cout << "channel " << channel.title << " went live" << std::endl; - if (channel.chat) { + if (channel.chat && channel.twitch_id != config.own_channel_id) { ShoutoutChannel(channel_id); } } else if (went_down) { @@ -165,7 +165,7 @@ private: void ShoutoutChannel(int id) { ChannelInfo &channel = state.GetChannelInfo(id); Shoutout &shout = renderer.CreateShoutout(id, state); - if (!channel.twitch_id.empty() && channel.twitch_id != config.own_channel_id) { + if (!channel.twitch_id.empty()) { if (config.shoutouts) { twitch_conn.Shoutout(config.own_channel_id, channel.twitch_id); } @@ -204,6 +204,24 @@ private: const ChannelInfo *channel = state.FindChannelInfo(name); if (channel) { ShoutoutChannel(channel->id); + } else { + SendIRCText("Channel nicht gefunden PoroSad"); + } + } else if (msg.StartsWith("!clip")) { + const ChannelInfo *channel = nullptr; + if (msg.GetText().length() > 6 && msg.GetText()[6] == '@') { + std::string name = msg.GetText().substr(7); + channel = state.FindChannelInfo(name); + } else if (msg.GetText().length() > 5) { + std::string name = msg.GetText().substr(6); + channel = state.FindChannelInfo(name); + } else { + channel = state.GetRandomChannel(); + } + if (channel) { + EnqueueClip(channel->id); + } else { + SendIRCText("Channel nicht gefunden NotLikeThis"); } } if (state.HasGame()) { @@ -211,6 +229,21 @@ private: } } + void EnqueueClip(int id) { + ChannelInfo &channel = state.GetChannelInfo(id); + ClipPlayer &player = renderer.CreateClip(id, state); + player.FetchClip(twitch_conn); + } + + void SendIRCText(const std::string &text) { + twitch::IRCMessage msg; + msg.command = "PRIVMSG"; + msg.params.push_back(config.chat_channel); + msg.params.push_back(text); + twitch_conn.SendMessage(msg); + } + + private: const Config &config; ffmpeg::Network net; diff --git a/src/app/ClipPlayer.cpp b/src/app/ClipPlayer.cpp new file mode 100644 index 0000000..988207a --- /dev/null +++ b/src/app/ClipPlayer.cpp @@ -0,0 +1,38 @@ +#include "ClipPlayer.h" + +#include + +#include "Media.h" +#include "State.h" + +namespace app { + +void ClipPlayer::LoadRandomClip(const Json::Value &json) { + const Json::Value &data = json["data"]; + if (!data.isArray()) return; + if (data.empty()) return; + Json::ArrayIndex num = data.size(); + std::uniform_int_distribution dist(0, num - 1); + Json::ArrayIndex choice = dist(state.GetRNG()); + const Json::Value &clip_json = data[choice]; + clip = twitch::Clip(clip_json); +} + +void ClipPlayer::Start(const Clock &clock) { + start_time = clock; + running = true; + if (clip.HasVideo()) { + std::cout << "adding clip " << clip.GetVideoURL() << " at " << clock << std::endl; + Media &media = state.AddMedia(clip.GetVideoURL().c_str()); + media.SetSyncPoint(clock); + media.AddWindow({ 0, 0, 1, 1 }, { 320, 150, 640, 360 }); + media.OnComplete([this](Media *) -> void { + done = true; + }); + } else { + std::cout << "clip has no video (" << clip.GetVideoURL() << ")" << std::endl; + done = true; + } +} + +} diff --git a/src/app/ClipPlayer.h b/src/app/ClipPlayer.h new file mode 100644 index 0000000..e201651 --- /dev/null +++ b/src/app/ClipPlayer.h @@ -0,0 +1,76 @@ +#ifndef TEST_APP_CLIPPLAYER_H_ +#define TEST_APP_CLIPPLAYER_H_ + +#include "ChannelInfo.h" +#include "Clock.h" +#include "../cairo/Context.h" +#include "../twitch/Clip.h" +#include "../ws/TwitchConnection.h" + +namespace app { + +class State; + +class ClipPlayer { + +public: + explicit ClipPlayer(const ChannelInfo &channel, cairo::Context &ctx, State &state) + : channel(channel) + , state(state) + , start_time() + , loading(false) + , running(false) + , done(false) { + } + +public: + bool Loading() const { + return loading; + } + + bool Running() const { + return running; + } + + bool Done() const { + return done; + } + + void FetchClip(ws::TwitchConnection &twitch) { + loading = true; + twitch.FetchClips(channel.twitch_id) + .Then([this](const Json::Value *rsp) -> void { + loading = false; + LoadRandomClip(*rsp); + }) + .Catch([this](ws::HttpsConnection *rsp) -> void { + loading = false; + done = true; + std::cout << "failed to fetch clips" << std::endl; + std::cout << rsp->GetBody() << std::endl; + }); + } + + void LoadRandomClip(const Json::Value &json); + + void Start(const Clock &clock); + + void Update(cairo::Context &ctx, const Clock &clock) { + } + +private: + const ChannelInfo &channel; + State &state; + + twitch::Clip clip; + + Clock start_time; + bool loading; + bool running; + bool done; + +}; + +} + +#endif diff --git a/src/app/Media.h b/src/app/Media.h index 7ecec17..50e54a0 100644 --- a/src/app/Media.h +++ b/src/app/Media.h @@ -9,11 +9,15 @@ #include "Window.h" #include "../cairo/Context.h" #include "../gfx/Rectangle.h" +#include "../sys/Callbacks.h" namespace app { class Media { +public: + typedef sys::Callbacks CallbacksType; + public: explicit Media(const char *url) : source(url) @@ -61,11 +65,20 @@ public: return source.IsEOF(); } + void OnComplete(CallbacksType::Callback cb) { + complete_callbacks.Listen(cb); + } + + void HandleComplete() { + complete_callbacks.FireNothrow(this); + } + private: Source source; cairo::Surface surface; Clock sync_point; std::vector windows; + CallbacksType complete_callbacks; }; diff --git a/src/app/Renderer.h b/src/app/Renderer.h index a632909..bdc6d4c 100644 --- a/src/app/Renderer.h +++ b/src/app/Renderer.h @@ -1,6 +1,7 @@ #ifndef TEST_APP_RENDERER_H_ #define TEST_APP_RENDERER_H_ +#include "ClipPlayer.h" #include "Shoutout.h" #include @@ -57,6 +58,11 @@ public: surface.Flush(); } + ClipPlayer &CreateClip(int channel_id, State &state) { + ClipPlayer &player = state.AddClip(channel_id, ctx); + return player; + } + Message &CreateMessage(State &state) { Message &msg = state.AddMessage(ctx); msg.SetTextFont(text_font); diff --git a/src/app/Shoutout.h b/src/app/Shoutout.h index 163778b..0bd3694 100644 --- a/src/app/Shoutout.h +++ b/src/app/Shoutout.h @@ -9,7 +9,6 @@ #include "../gfx/Spacing.h" #include "../twitch/Clip.h" #include "../ws/TwitchConnection.h" -#include "json/forwards.h" #include "json/value.h" #include diff --git a/src/app/State.h b/src/app/State.h index a072662..fb24c2e 100644 --- a/src/app/State.h +++ b/src/app/State.h @@ -1,6 +1,7 @@ #ifndef TEST_APP_STATE_H_ #define TEST_APP_STATE_H_ +#include #include #include #include @@ -8,6 +9,7 @@ #include #include "ChannelInfo.h" +#include "ClipPlayer.h" #include "Clock.h" #include "Game.h" #include "Media.h" @@ -73,6 +75,17 @@ public: return nullptr; } + ChannelInfo *GetRandomChannel() { + if (channels.empty()) { + return nullptr; + } + std::uniform_int_distribution dist(0, channels.size() - 1); + size_t n = dist(GetRNG()); + auto iter = channels.begin(); + std::advance(iter, n); + return &iter->second; + } + const std::list &GetMedia() const { return media; } @@ -85,7 +98,15 @@ public: return shoutouts; } + ClipPlayer &AddClip(int channel_id, cairo::Context &ctx) { + const ChannelInfo &info = GetChannelInfo(channel_id); + std::cout << "adding clip from channel " << info.title << std::endl; + clips.emplace_back(info, ctx, *this); + return clips.back(); + } + Media &AddMedia(const char *url) { + std::cout << "adding media " << url << std::endl; media.emplace_back(url); return media.back(); } @@ -96,7 +117,9 @@ public: } Shoutout &AddShoutout(int channel_id, cairo::Context &ctx) { - shoutouts.emplace_back(GetChannelInfo(channel_id), ctx, *this); + const ChannelInfo &info = GetChannelInfo(channel_id); + std::cout << "adding shoutout for channel " << info.title << std::endl; + shoutouts.emplace_back(info, ctx, *this); return shoutouts.back(); } @@ -136,6 +159,15 @@ public: if (HasGame()) { GetGame().Update(ctx, clock); } + if (!clips.empty()) { + if (clips.front().Done()) { + clips.pop_front(); + } else if (clips.front().Running()) { + clips.front().Update(ctx, clock); + } else if (!clips.front().Loading()) { + clips.front().Start(clock); + } + } if (!shoutouts.empty()) { if (shoutouts.front().Done()) { shoutouts.pop_front(); @@ -151,6 +183,7 @@ public: for (auto m = media.begin(); m != media.end();) { if (m->IsEOF()) { std::cout << "removing EOF media" << std::endl; + m->HandleComplete(); m = media.erase(m); } else { ++m; @@ -169,6 +202,7 @@ private: std::map channels; + std::list clips; std::list media; std::list msgs; std::list shoutouts;