#include <json/value.h>
#include "ChannelInfo.h"
+#include "ClipPlayer.h"
#include "Config.h"
#include "DrawingGame.h"
#include "Mixer.h"
ChannelInfo &info = state.GetChannelInfo(channel_id);
info.Update(channel);
}
- ShoutoutChannel(33);
}
void HandlePusherChannel(const Json::Value &json) {
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) {
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);
}
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()) {
}
}
+ 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;
--- /dev/null
+#include "ClipPlayer.h"
+
+#include <random>
+
+#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<std::mt19937::result_type> 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;
+ }
+}
+
+}
--- /dev/null
+#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
#include "Window.h"
#include "../cairo/Context.h"
#include "../gfx/Rectangle.h"
+#include "../sys/Callbacks.h"
namespace app {
class Media {
+public:
+ typedef sys::Callbacks<Media *> CallbacksType;
+
public:
explicit Media(const char *url)
: source(url)
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<Window> windows;
+ CallbacksType complete_callbacks;
};
#ifndef TEST_APP_RENDERER_H_
#define TEST_APP_RENDERER_H_
+#include "ClipPlayer.h"
#include "Shoutout.h"
#include <cstdint>
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);
#include "../gfx/Spacing.h"
#include "../twitch/Clip.h"
#include "../ws/TwitchConnection.h"
-#include "json/forwards.h"
#include "json/value.h"
#include <cstdint>
#ifndef TEST_APP_STATE_H_
#define TEST_APP_STATE_H_
+#include <iterator>
#include <list>
#include <map>
#include <ostream>
#include <unicode/unistr.h>
#include "ChannelInfo.h"
+#include "ClipPlayer.h"
#include "Clock.h"
#include "Game.h"
#include "Media.h"
return nullptr;
}
+ ChannelInfo *GetRandomChannel() {
+ if (channels.empty()) {
+ return nullptr;
+ }
+ std::uniform_int_distribution<std::mt19937::result_type> dist(0, channels.size() - 1);
+ size_t n = dist(GetRNG());
+ auto iter = channels.begin();
+ std::advance(iter, n);
+ return &iter->second;
+ }
+
const std::list<Media> &GetMedia() const {
return media;
}
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();
}
}
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();
}
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();
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;
std::map<int, ChannelInfo> channels;
+ std::list<ClipPlayer> clips;
std::list<Media> media;
std::list<Message> msgs;
std::list<Shoutout> shoutouts;