$ git clone https://tcclient.ion.nu/tc_client.git
commit 7390eed87187c658daa1cdd0182d8b3278e886e8
Author: Alicia <...>
Date: Tue Apr 7 06:49:01 2015 +0200
Version 0.22
diff --git a/ChangeLog b/ChangeLog
index a403f50..424a400 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,12 @@
+0.22:
+Cleaned up media.c/h which I forgot to clean up before releasing 0.21.
+Detect when someone cams down.
+Added a /names command to list who is online.
+camviewer: fall back to GTK+2 if GTK+3 is not available.
+camviewer: show multiple cameras (cams to view are now picked up from who is on cam when joining and whenever someone cams up instead of from argv[1])
+modbot: list at most 5 not yet approved videos for !queue to avoid sending messages which are too large.
+modbot: fixed a bug where removing a non-approved video from queue using !wrongrequest and that video was the first in queue, the second video in queue wouldn't be played.
+modbot: added !whorequested to see who requested the currently playing video.
0.21:
Renamed streamids to chunkids to better match the specification.
Added a /close command for mods to close cam/mic streams.
diff --git a/Makefile b/Makefile
index 7cdc521..f943ec4 100644
--- a/Makefile
+++ b/Makefile
@@ -1,4 +1,4 @@
-VERSION=0.21
+VERSION=0.22
CFLAGS=-g3 -Wall $(shell curl-config --cflags)
LIBS=-g3 $(shell curl-config --libs)
ifneq ($(wildcard config.mk),)
diff --git a/amfparser.c b/amfparser.c
index 8dfaf51..35e9a76 100644
--- a/amfparser.c
+++ b/amfparser.c
@@ -177,6 +177,16 @@ void amf_free(struct amf* amf)
free(amf);
}
+struct amfitem* amf_getobjmember(struct amfobject* obj, const char* name)
+{
+ unsigned int i;
+ for(i=0; i<obj->membercount; ++i)
+ {
+ if(!strcmp(obj->members[i].name, name)){return &obj->members[i].value;}
+ }
+ return 0;
+}
+
void printamfobject(struct amfobject* obj, int indent)
{
int i, j;
diff --git a/amfparser.h b/amfparser.h
index fda86f0..96d9a1c 100644
--- a/amfparser.h
+++ b/amfparser.h
@@ -65,5 +65,6 @@ extern char amf_compareitems(struct amfitem* a, struct amfitem* b);
extern struct amf* amf_parse(const unsigned char* buf, int len);
extern void amf_free(struct amf* amf);
+extern struct amfitem* amf_getobjmember(struct amfobject* obj, const char* name);
extern void printamf(struct amf* amf);
diff --git a/client.c b/client.c
index bb2c397..e7f8824 100644
--- a/client.c
+++ b/client.c
@@ -400,7 +400,8 @@ int main(int argc, char** argv)
"/close <nick> = close someone's cam/mic stream (as a mod)\n"
"/ban <nick> = ban someone\n"
"/banlist = list who is banned\n"
- "/forgive <nick/ID> = unban someone\n");
+ "/forgive <nick/ID> = unban someone\n"
+ "/names = list everyone that is online\n");
fflush(stdout);
}
else if(!strncmp(buf, "/color", 6) && (!buf[6]||buf[6]==' '))
@@ -510,6 +511,17 @@ int main(int argc, char** argv)
amfsend(&amf, sock);
continue;
}
+ else if(!strcmp(buf, "/names"))
+ {
+ printf("Currently online: ");
+ for(i=0; i<idlistlen; ++i)
+ {
+ printf("%s%s", (i?", ":""), idlist[i].name);
+ }
+ printf("\n");
+ fflush(stdout);
+ continue;
+ }
}
char* msg=tonumlist(buf, len);
amfinit(&amf, 3);
@@ -742,7 +754,7 @@ int main(int argc, char** argv)
for(;len<10; ++len){printf(" ");}
printf(" %s\n", amfin->items[i+1].string.string);
}
- printf("Use /forgive <ID> to unban someone\n");
+ printf("Use /forgive <ID/nick> to unban someone\n");
fflush(stdout);
}
// "avons", 0, "ID1", "nick1", "IDn", "nickn"...
@@ -775,6 +787,10 @@ int main(int argc, char** argv)
// Creating a new stream worked, now play media (cam/mic) on it (if that's what the result was for)
stream_play(amfin, sock);
}
+ else if(amfin->itemcount>0 && amfin->items[0].type==AMF_STRING && amf_comparestrings_c(&amfin->items[0].string, "onStatus"))
+ {
+ stream_handlestatus(amfin);
+ }
// else{printf("Unknown command...\n"); printamf(amfin);} // (Debugging)
amf_free(amfin);
}
diff --git a/configure b/configure
index 517c8ec..955fecc 100755
--- a/configure
+++ b/configure
@@ -39,17 +39,26 @@ fi
rm -f iconvtest iconvtest.c
printf 'Checking for gtk+-3.0... '
-libs="`pkg-config --libs gtk+-3.0`"
+libs="`pkg-config --libs gtk+-3.0 2> /dev/null`"
if [ "x$libs" != "x" ]; then
echo "GTK_LIBS=${libs}" >> config.mk
echo "GTK_CFLAGS=`pkg-config --cflags gtk+-3.0`" >> config.mk
echo yes
else
echo no
+ printf 'Checking for gtk+-2.0... '
+ libs="`pkg-config --libs gtk+-2.0 2> /dev/null`"
+ if [ "x$libs" != "x" ]; then
+ echo "GTK_LIBS=${libs}" >> config.mk
+ echo "GTK_CFLAGS=`pkg-config --cflags gtk+-2.0`" >> config.mk
+ echo yes
+ else
+ echo no
+ fi
fi
printf 'Checking for libavcodec... '
-libs="`pkg-config --libs libavcodec`"
+libs="`pkg-config --libs libavcodec 2> /dev/null`"
if [ "x$libs" != "x" ]; then
echo "AVCODEC_LIBS=${libs}" >> config.mk
echo "AVCODEC_CFLAGS=`pkg-config --cflags libavcodec`" >> config.mk
@@ -59,7 +68,7 @@ else
fi
printf 'Checking for libswscale... '
-libs="`pkg-config --libs libswscale`"
+libs="`pkg-config --libs libswscale 2> /dev/null`"
if [ "x$libs" != "x" ]; then
echo "SWSCALE_LIBS=${libs}" >> config.mk
echo "SWSCALE_CFLAGS=`pkg-config --cflags libswscale`" >> config.mk
@@ -69,7 +78,7 @@ else
fi
printf 'Checking for libavutil... '
-libs="`pkg-config --libs libavutil`"
+libs="`pkg-config --libs libavutil 2> /dev/null`"
if [ "x$libs" != "x" ]; then
echo "AVUTIL_LIBS=${libs}" >> config.mk
echo "AVUTIL_CFLAGS=`pkg-config --cflags libavutil`" >> config.mk
diff --git a/media.c b/media.c
index 71d54e9..18157be 100644
--- a/media.c
+++ b/media.c
@@ -16,19 +16,11 @@
*/
#include <stdio.h>
#include <stdlib.h>
-//#include "rtmp.h"
-//#include "amfparser.h"
+#include <string.h>
#include "endian.h"
#include "media.h"
#include "amfwriter.h"
#include "idlist.h"
-/*
-struct stream
-{
- unsigned int streamid;
- unsigned int userid;
-};
-*/
struct stream* streams=0;
unsigned int streamcount=0;
@@ -43,14 +35,9 @@ char stream_idtaken(unsigned int id)
return 0;
}
-void stream_start(const char* nick, int sock) // called upon privmsg "/cam ..."
+void stream_start(const char* nick, int sock) // called upon privmsg "/opencam ..."
{
unsigned int userid=idlist_get(nick);
-/*
- unsigned int id=idlist_get((char*)&buf[5]);
- camid=malloc(128);
- sprintf(camid, "%u", id);
-*/
unsigned int streamid=1;
while(stream_idtaken(streamid)){++streamid;}
++streamcount;
@@ -90,16 +77,40 @@ void stream_play(struct amf* amf, int sock) // called upon _result
void stream_handledata(struct rtmp* rtmp)
{
-rtmp->msgid=1;
unsigned int i;
for(i=0; i<streamcount; ++i)
{
if(streams[i].streamid!=rtmp->msgid){continue;}
- printf("Video: %u %u\n", streams[i].userid, rtmp->length); // TODO: if this becomes permanent we will have to specify a nick or ID or something
-// write(1, rtmp.buf, rtmp.length);
+// fprintf(stderr, "Chunk: chunkid: %u, streamid: %u, userid: %u\n", rtmp->chunkid, rtmp->msgid, streams[i].userid);
+ printf("Video: %u %u\n", streams[i].userid, rtmp->length);
fwrite(rtmp->buf, rtmp->length, 1, stdout);
fflush(stdout);
return;
}
printf("Received media data to unknown stream ID %u\n", rtmp->msgid);
}
+
+void stream_handlestatus(struct amf* amf)
+{
+ if(amf->itemcount<3 || amf->items[2].type!=AMF_OBJECT){return;}
+ struct amfobject* obj=&amf->items[2].object;
+ struct amfitem* code=amf_getobjmember(obj, "code");
+ struct amfitem* details=amf_getobjmember(obj, "details");
+ if(!code || !details){return;}
+ if(code->type!=AMF_STRING || details->type!=AMF_STRING){return;}
+ if(!strcmp(code->string.string, "NetStream.Play.Stop"))
+ {
+ unsigned int id=strtoul(details->string.string, 0, 0);
+ unsigned int i;
+ for(i=0; i<streamcount; ++i)
+ {
+ if(streams[i].userid==id)
+ {
+ printf("VideoEnd: %u\n", streams[i].userid);
+// TODO: remove stream from array
+// TODO: destroyStream or something?
+ return;
+ }
+ }
+ }
+}
diff --git a/media.h b/media.h
index 58850d4..728c3dd 100644
--- a/media.h
+++ b/media.h
@@ -25,6 +25,7 @@ struct stream
extern struct stream* streams;
extern unsigned int streamcount;
-extern void stream_start(const char* nick, int sock); // called upon privmsg "/cam ..."
+extern void stream_start(const char* nick, int sock); // called upon privmsg "/opencam ..."
extern void stream_play(struct amf* amf, int sock); // called upon _result
extern void stream_handledata(struct rtmp* rtmp);
+extern void stream_handlestatus(struct amf* amf);
diff --git a/rtmp.c b/rtmp.c
index 3c5c82b..ab2c29f 100644
--- a/rtmp.c
+++ b/rtmp.c
@@ -49,6 +49,7 @@ struct chunk* chunk_get(unsigned int id)
chunks[i].buf=0;
chunks[i].timestamp=0;
chunks[i].length=0;
+ chunks[i].type=0;
return &chunks[i];
}
@@ -101,7 +102,7 @@ char rtmp_get(int sock, struct rtmp* rtmp)
unsigned int rsize=((chunk->length-chunk->pos>127)?128:(chunk->length-chunk->pos));
while(rsize>0)
{
- size_t r=read(sock, chunk->buf+chunk->pos, rsize);;
+ size_t r=read(sock, chunk->buf+chunk->pos, rsize);
if(r<1){return 0;}
rsize-=r;
chunk->pos+=r;
diff --git a/utilities/camviewer/camviewer.c b/utilities/camviewer/camviewer.c
index 51e6738..dca0f35 100644
--- a/utilities/camviewer/camviewer.c
+++ b/utilities/camviewer/camviewer.c
@@ -20,13 +20,26 @@
#include <libswscale/swscale.h>
#include <gtk/gtk.h>
-struct viddata
+#if GTK_MAJOR_VERSION==2
+ #define gtk_box_new(orientation, spacing) gtk_hbox_new(1, spacing)
+#endif
+
+struct camera
{
AVFrame* frame;
AVFrame* dstframe;
GtkWidget* cam;
AVCodecContext* ctx;
- char* camnick;
+ char* id;
+ char* nick;
+};
+
+struct viddata
+{
+ struct camera* cams;
+ unsigned int camcount;
+ GtkWidget* box;
+ AVCodec* decoder;
};
int tc_client[2];
@@ -36,68 +49,138 @@ char buf[1024];
gboolean handledata(GIOChannel* channel, GIOCondition condition, gpointer datap)
{
struct viddata* data=datap;
- int i;
+ unsigned int i;
for(i=0; i<1023; ++i)
{
if(read(tc_client[0], &buf[i], 1)<1){printf("No more data\n"); gtk_main_quit(); return 0;}
if(buf[i]=='\r'||buf[i]=='\n'){break;}
}
buf[i]=0;
- // Start stream once we're properly connected
- if(!strncmp(buf, "Connection ID: ", 10)){dprintf(tc_client_in[1], "/opencam %s\n", data->camnick); return 1;}
- if(strncmp(buf, "Video: ", 7)){printf("Got '%s'\n", buf); return 1;} // Ignore anything that isn't video
+ // Start streams once we're properly connected
+ if(!strncmp(buf, "Currently on cam: ", 18))
+ {
+ char* next=&buf[16];
+ while(next)
+ {
+ char* user=&next[2];
+ next=strstr(user, ", ");
+ if(next){next[0]=0;}
+ dprintf(tc_client_in[1], "/opencam %s\n", user);
+ }
+ return 1;
+ }
+ char* space=strchr(buf, ' ');
+ // Start a stream when someone cams up
+ if(space && !strcmp(space, " cammed up"))
+ {
+ space[0]=0;
+ dprintf(tc_client_in[1], "/opencam %s\n", buf);
+ return 1;
+ }
+ if(!strncmp(buf, "Starting media stream for ", 26))
+ {
+ char* nick=&buf[26];
+ char* id=strstr(nick, " (");
+ if(!id){return 1;}
+ id[0]=0;
+ id=&id[2];
+ char* idend=strchr(id, ')');
+ if(!idend){return 1;}
+ idend[0]=0;
+ ++data->camcount;
+ data->cams=realloc(data->cams, sizeof(struct camera)*data->camcount);
+ struct camera* cam=&data->cams[data->camcount-1];
+ cam->frame=av_frame_alloc();
+ cam->dstframe=av_frame_alloc();
+ cam->nick=strdup(nick);
+ cam->id=strdup(id);
+ cam->ctx=avcodec_alloc_context3(data->decoder);
+ avcodec_open2(cam->ctx, data->decoder, 0);
+ cam->cam=gtk_image_new();
+ gtk_box_pack_start(GTK_BOX(data->box), cam->cam, 0, 0, 0);
+ gtk_widget_show(cam->cam);
+ return 1;
+ }
+ if(!strncmp(buf, "VideoEnd: ", 10))
+ {
+ for(i=0; i<data->camcount; ++i)
+ {
+ if(!strcmp(data->cams[i].id, &buf[10]))
+ {
+ gtk_widget_destroy(data->cams[i].cam);
+ av_frame_free(&data->cams[i].frame);
+ avcodec_free_context(&data->cams[i].ctx);
+ free(data->cams[i].id);
+ free(data->cams[i].nick);
+ --data->camcount;
+ memmove(&data->cams[i], &data->cams[i+1], (data->camcount-i)*sizeof(struct camera));
+ break;
+ }
+ }
+ return 1;
+ }
+ if(strncmp(buf, "Video: ", 7)){printf("Got '%s'\n", buf); return 1;} // Ignore anything else that isn't video
char* sizestr=strchr(&buf[7], ' ');
+ if(!sizestr){return 1;}
+ sizestr[0]=0;
+ // Find the camera representation for the given ID
+ struct camera* cam=0;
+ for(i=0; i<data->camcount; ++i)
+ {
+ if(!strcmp(data->cams[i].id, &buf[7])){cam=&data->cams[i]; break;}
+ }
unsigned int size=strtoul(&sizestr[1], 0, 0);
- // Mostly ignore the first byte (contains frame type (e.g. keyframe etc.) in 4 bits and codec in the other 4, but we assume FLV1)
+ // Mostly ignore the first byte (contains frame type (e.g. keyframe etc.) in 4 bits and codec in the other 4)
--size;
AVPacket pkt;
av_init_packet(&pkt);
- pkt.data=malloc(size);
- read(tc_client[0], pkt.data, 1); // Skip
-// printf("Frametype-frame: %x\n", ((unsigned int)pkt.data[0]&0xf0)/16);
-// printf("Frametype-codec: %x\n", (unsigned int)pkt.data[0]&0xf);
- if((pkt.data[0]&0xf)!=2) // Not FLV1, get data but discard it
+ unsigned char databuf[size+4];
+ pkt.data=databuf;
+ unsigned char frameinfo;
+ read(tc_client[0], &frameinfo, 1);
+// printf("Frametype-frame: %x\n", ((unsigned int)frameinfo&0xf0)/16);
+// printf("Frametype-codec: %x\n", (unsigned int)frameinfo&0xf);
+ unsigned int pos=0;
+ while(pos<size)
{
- read(tc_client[0], pkt.data, size);
- return 1;
+ pos+=read(tc_client[0], pkt.data+pos, size-pos);
}
- read(tc_client[0], pkt.data, size);
+ if((frameinfo&0xf)!=2){return 1;} // Not FLV1, get data but discard it
+ if(!cam){printf("No cam found with ID '%s'\n", &buf[7]); free(pkt.data); return 1;}
pkt.size=size;
int gotframe;
- avcodec_decode_video2(data->ctx, data->frame, &gotframe, &pkt);
- free(pkt.data);
+ avcodec_decode_video2(cam->ctx, cam->frame, &gotframe, &pkt);
if(!gotframe){return 1;}
// Convert to RGB24 format
- unsigned int bufsize=avpicture_get_size(PIX_FMT_RGB24, data->frame->width, data->frame->height);
+ unsigned int bufsize=avpicture_get_size(PIX_FMT_RGB24, cam->frame->width, cam->frame->height);
unsigned char buf[bufsize];
- data->dstframe->data[0]=buf;
- data->dstframe->linesize[0]=data->frame->width*3;
- struct SwsContext* swsctx=sws_getContext(data->frame->width, data->frame->height, data->frame->format, data->frame->width, data->frame->height, AV_PIX_FMT_RGB24, 0, 0, 0, 0);
- sws_scale(swsctx, (const uint8_t*const*)data->frame->data, data->frame->linesize, 0, data->frame->height, data->dstframe->data, data->dstframe->linesize);
+ cam->dstframe->data[0]=buf;
+ cam->dstframe->linesize[0]=cam->frame->width*3;
+ struct SwsContext* swsctx=sws_getContext(cam->frame->width, cam->frame->height, cam->frame->format, cam->frame->width, cam->frame->height, AV_PIX_FMT_RGB24, 0, 0, 0, 0);
+ sws_scale(swsctx, (const uint8_t*const*)cam->frame->data, cam->frame->linesize, 0, cam->frame->height, cam->dstframe->data, cam->dstframe->linesize);
+ sws_freeContext(swsctx);
- GdkPixbuf* gdkframe=gdk_pixbuf_new_from_data(data->dstframe->data[0], GDK_COLORSPACE_RGB, 0, 8, data->frame->width, data->frame->height, data->dstframe->linesize[0], 0, 0);
- gtk_image_set_from_pixbuf(GTK_IMAGE(data->cam), gdkframe);
- g_object_unref(gdkframe);
+ GdkPixbuf* gdkframe=gdk_pixbuf_new_from_data(cam->dstframe->data[0], GDK_COLORSPACE_RGB, 0, 8, cam->frame->width, cam->frame->height, cam->dstframe->linesize[0], 0, 0);
+ gtk_image_set_from_pixbuf(GTK_IMAGE(cam->cam), gdkframe);
+ // Make sure it gets redrawn in time
+ gdk_window_process_updates(gtk_widget_get_window(cam->cam), 1);
+ g_object_unref(gdkframe);
return 1;
}
int main(int argc, char** argv)
{
- struct viddata data={av_frame_alloc(),av_frame_alloc()};
- data.camnick=argv[1];
- // Init the decoder
+ struct viddata data={0,0,0,0};
avcodec_register_all();
- AVCodec* decoder=avcodec_find_decoder(AV_CODEC_ID_FLV1);
- data.ctx=avcodec_alloc_context3(decoder);
- avcodec_open2(data.ctx, decoder, 0);
+ data.decoder=avcodec_find_decoder(AV_CODEC_ID_FLV1);
gtk_init(&argc, &argv);
GtkWidget* w=gtk_window_new(GTK_WINDOW_TOPLEVEL);
g_signal_connect(w, "destroy", gtk_main_quit, 0);
- data.cam=gtk_image_new();
- gtk_container_add(GTK_CONTAINER(w), data.cam);
+ data.box=gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
+ gtk_container_add(GTK_CONTAINER(w), data.box);
gtk_widget_show_all(w);
pipe(tc_client);
@@ -108,18 +191,27 @@ int main(int argc, char** argv)
close(tc_client_in[1]);
dup2(tc_client[1], 1);
dup2(tc_client_in[0], 0);
- argv[1]="./tc_client";
- execv("./tc_client", &argv[1]);
+ argv[0]="./tc_client";
+ execv("./tc_client", argv);
}
close(tc_client[1]);
close(tc_client_in[0]);
GIOChannel* tcchannel=g_io_channel_unix_new(tc_client[0]);
g_io_channel_set_encoding(tcchannel, 0, 0);
- g_io_add_watch(tcchannel, G_IO_IN, handledata, &data);
+ unsigned int channel_id=g_io_add_watch(tcchannel, G_IO_IN, handledata, &data);
gtk_main();
- free(data.frame->data[0]);
- av_frame_free(&data.frame);
+ g_source_remove(channel_id);
+ g_io_channel_shutdown(tcchannel, 0, 0);
+ unsigned int i;
+ for(i=0; i<data.camcount; ++i)
+ {
+ av_frame_free(&data.cams[i].frame);
+ avcodec_free_context(&data.cams[i].ctx);
+ free(data.cams[i].id);
+ free(data.cams[i].nick);
+ }
+ free(data.cams);
return 0;
}
diff --git a/utilities/modbot/modbot.c b/utilities/modbot/modbot.c
index 87155b9..90eec60 100644
--- a/utilities/modbot/modbot.c
+++ b/utilities/modbot/modbot.c
@@ -34,6 +34,7 @@ struct queue queue={0,0};
struct list goodvids={0,0}; // pre-approved videos
struct list badvids={0,0}; // not allowed, essentially banned
char* playing=0;
+char* requester=0;
time_t started=0;
int tc_client;
@@ -105,7 +106,7 @@ void playnextvid()
{
waitskip=0;
playing=queue.items[0].video;
- free(queue.items[0].requester);
+ requester=queue.items[0].requester;
free(queue.items[0].title);
--queue.itemcount;
memmove(queue.items, &queue.items[1], sizeof(struct queueitem)*queue.itemcount);
@@ -118,7 +119,9 @@ void playnextvid()
void playnext(int x)
{
free(playing);
+ free(requester);
playing=0;
+ requester=0;
if(queue.itemcount<1){alarm(0); printf("Nothing more to play\n"); return;} // Nothing more to play
if(!list_contains(&goodvids, queue.items[0].video))
{
@@ -297,6 +300,7 @@ int main(int argc, char** argv)
if(!strcmp(queue.items[i].requester, nick))
{
queue_del(&queue, queue.items[i].video);
+ if(!playing && i==0){playnext(0);}
break;
}
}
@@ -332,22 +336,29 @@ int main(int argc, char** argv)
{
char buf[len];
buf[0]=0;
+ unsigned int listed=0;
for(i=0; i<queue.itemcount; ++i)
{
- if(!list_contains(&goodvids, queue.items[i].video))
+ if(listed<5 && !list_contains(&goodvids, queue.items[i].video))
{
if(buf[0]){strcat(buf, ", ");}
strcat(buf, queue.items[i].video);
strcat(buf, " (");
strcat(buf, queue.items[i].title);
strcat(buf, ")");
+ ++listed;
}
}
- say(pm, "%u videos in queue, %u of which are not yet approved by mods (%s)\n", queue.itemcount, notapproved, buf);
+ say(pm, "%u videos in queue, %u of which are not yet approved by mods (%s%s)\n", queue.itemcount, notapproved, buf, (listed<notapproved)?", etc.":"");
}else{
say(pm, "%u videos in queue\n", queue.itemcount);
}
}
+ else if(!strcmp(msg, "!whorequested"))
+ {
+ if(!playing){say(pm, "Nothing is playing\n");}
+ else{say(pm, "%s requested %s\n", requester, playing);}
+ }
else if(!strcmp(msg, "!time")) // Debugging
{
unsigned int remaining=alarm(0);