/// \file player.cpp /// Holds all code for the MistPlayer application used for VoD streams. #include //for std::cerr #include //for fileno #include //for atoi #include #include #include #include #include #include #include #include //under cygwin, recv blocks for ~15ms if no data is available. //This is a hack to keep performance decent with that bug present. #ifdef __CYGWIN__ #define CYG_DEFI int cyg_count; #define CYG_INCR cyg_count++; #define CYG_LOOP (cyg_count % 20 == 0) && #else #define CYG_DEFI #define CYG_INCR #define CYG_LOOP #endif ///Converts a stats line to up, down, host, connector and conntime values. class Stats{ public: unsigned int up;///= 0; i--){ result[i] = number & 0xFF; number >>= 8; } return result; } int main(int argc, char** argv){ Util::Config conf(argv[0], PACKAGE_VERSION); conf.addOption("filename", JSON::fromString("{\"arg_num\":1, \"help\":\"Name of the file to write to stdout.\"}")); conf.addOption("streamname", JSON::fromString("{\"arg\":\"string\",\"short\":\"s\",\"long\":\"stream\",\"help\":\"The name of the stream that this connector will transmit.\"}")); conf.parseArgs(argc, argv); conf.activate(); int playing = 0; Socket::Connection in_out = Socket::Connection(fileno(stdout), fileno(stdin)); DTSC::File source = DTSC::File(conf.getString("filename")); if ( !source.getMeta().isFixed()){ std::cerr << "Encountered a non-fixed file." << std::endl; return 1; } std::string streamname = conf.getString("streamname"); source.getMeta().send(in_out); JSON::Value pausemark; pausemark["trackid"] = 0ll; pausemark["mark"] = "pause"; pausemark["time"] = (long long int)0; Socket::Connection StatsSocket = Socket::Connection(Util::getTmpFolder() + "statistics", true); int lasttime = Util::epoch(); //time last packet was sent JSON::Value last_pack; bool meta_sent = false; int playUntil = -1; long long now, lastTime = 0; //for timing of sending packets long long bench = 0; //for benchmarking std::set newSelect; Stats sts; CYG_DEFI while (in_out.connected() && (Util::epoch() - lasttime < 60) && conf.is_active){ CYG_INCR if (CYG_LOOP in_out.spool()){ while (in_out.Received().size()){ //delete anything that doesn't end with a newline if ( *(in_out.Received().get().rbegin()) != '\n'){ in_out.Received().get().clear(); continue; } in_out.Received().get().resize(in_out.Received().get().size() - 1); if ( !in_out.Received().get().empty()){ switch (in_out.Received().get()[0]){ case 'P': { //Push #if DEBUG >= 4 std::cerr << "Received push - ignoring (" << in_out.Received().get() << ")" << std::endl; #endif in_out.close(); //pushing to VoD makes no sense break; } case 'S': { //Stats if ( !StatsSocket.connected()){ StatsSocket = Socket::Connection(Util::getTmpFolder() + "statistics", true); } if (StatsSocket.connected()){ sts = Stats(in_out.Received().get().substr(2)); JSON::Value json_sts; json_sts["vod"]["down"] = (long long int)sts.down; json_sts["vod"]["up"] = (long long int)sts.up; json_sts["vod"]["time"] = (long long int)sts.conntime; json_sts["vod"]["host"] = sts.host; json_sts["vod"]["connector"] = sts.connector; json_sts["vod"]["filename"] = conf.getString("filename"); json_sts["vod"]["now"] = Util::epoch(); json_sts["vod"]["start"] = Util::epoch() - sts.conntime; if ( !meta_sent){ json_sts["vod"]["meta"] = source.getMeta().toJSON(); json_sts["vod"]["meta"]["is_fixed"] = 1; for (JSON::ObjIter oIt = json_sts["vod"]["meta"]["tracks"].ObjBegin(); oIt != json_sts["vod"]["meta"]["tracks"].ObjEnd(); oIt++){ oIt->second.removeMember("keys"); oIt->second.removeMember("fragments"); oIt->second.removeMember("parts"); } meta_sent = true; } StatsSocket.SendNow(json_sts.toString()); StatsSocket.SendNow("\n\n", 2); StatsSocket.flush(); } break; } case 's': { //second-seek int ms = JSON::Value(in_out.Received().get().substr(2)).asInt(); source.seek_time(ms); lasttime = Util::epoch(); lastTime = 0; playUntil = 0; break; } case 'p': { //play playing = -1; lasttime = Util::epoch(); in_out.setBlocking(false); if (in_out.Received().get().size() >= 2){ playUntil = atoi(in_out.Received().get().substr(2).c_str()); lastTime = 0; bench = Util::getMS(); }else{ playUntil = 0; } break; } case 'o': { //once-play if (playing <= 0){ playing = 1; } ++playing; in_out.setBlocking(false); bench = Util::getMS(); break; } case 'q': { //quit-playing playing = 0; in_out.setBlocking(true); break; } case 't': { newSelect.clear(); std::string tmp = in_out.Received().get().substr(2); while (tmp != ""){ newSelect.insert(atoi(tmp.substr(0,tmp.find(' ')).c_str())); if (tmp.find(' ') != std::string::npos){ tmp.erase(0,tmp.find(' ')+1); }else{ tmp = ""; } } source.selectTracks(newSelect); break; } #if DEBUG >= 4 default: { std::cerr << "MistPlayer received an unknown command: " << in_out.Received().get() << std::endl; break; } #endif } in_out.Received().get().clear(); } } } if (playing != 0){ now = Util::getMS(); source.seekNext(); if ( !source.getJSON()){ playing = 0; } if (playing > 0 && source.atKeyframe()){ --playing; } if (lastTime == 0){ lastTime = now - source.getJSON()["time"].asInt(); } if (playing == -1 && playUntil == 0 && source.getJSON()["time"].asInt() > now - lastTime + 7500){ Util::sleep(source.getJSON()["time"].asInt() - (now - lastTime + 5000)); } if ( playUntil && playUntil <= source.getJSON()["time"].asInt()){ playing = 0; } if (playing == 0){ #if DEBUG >= 4 std::cerr << "Completed VoD request in MistPlayer (" << (Util::getMS() - bench) << "ms)" << std::endl; #endif pausemark["time"] = source.getJSON()["time"]; pausemark.sendTo(in_out); in_out.setBlocking(true); }else{ lasttime = Util::epoch(); source.getJSON().sendTo(in_out); } }else{ Util::sleep(10); } } StatsSocket.close(); in_out.close(); #if DEBUG >= 5 if (Util::epoch() - lasttime < 60){ std::cerr << "MistPlayer exited (disconnect)." << std::endl; }else{ std::cerr << "MistPlayer exited (command timeout)." << std::endl; } #endif return 0; }