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