From acea07e3eccba2bb0f525e4e54da77f568240d9f Mon Sep 17 00:00:00 2001 From: Marco van Dijk Date: Mon, 5 Aug 2024 15:17:21 +0200 Subject: [PATCH 01/10] Util: add `fstream` import, which was removed from `urireader` --- lib/util.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/util.cpp b/lib/util.cpp index 012ea106..e488e4aa 100644 --- a/lib/util.cpp +++ b/lib/util.cpp @@ -20,6 +20,7 @@ #endif #include #include +#include #define RAXHDR_FIELDOFFSET p[1] #define RAX_REQDFIELDS_LEN 36 From c379a9e686bae135d5a00ee554e8f2d4a8b11e62 Mon Sep 17 00:00:00 2001 From: Marco van Dijk Date: Wed, 29 May 2024 11:59:50 +0200 Subject: [PATCH 02/10] Fixes to urireader: continue without range request support if HEAD request fails; close socket on error --- lib/urireader.cpp | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/lib/urireader.cpp b/lib/urireader.cpp index 92f288fe..b30809d6 100644 --- a/lib/urireader.cpp +++ b/lib/urireader.cpp @@ -213,7 +213,6 @@ namespace HTTP{ downer.getSocket().close(); downer.getSocket().Received().clear(); allData.truncate(0); - if (!downer.isOk()){return false;} supportRangeRequest = false; totalSize = std::string::npos; }else{ @@ -334,7 +333,16 @@ namespace HTTP{ // Note: this function returns true if the full read was completed only. // It's the reason this function returns void rather than bool. size_t prev = cb.getDataCallbackPos(); - downer.continueNonBlocking(cb); + if (downer.continueNonBlocking(cb)){ + if (downer.getStatusCode() >= 400){ + WARN_MSG("Received error response code %" PRIu32 " (%s)", downer.getStatusCode(), downer.getStatusText().c_str()); + // cb.dataCallbackFlush(); + downer.getSocket().close(); + downer.getSocket().Received().clear(); + allData.truncate(0); + return 0; + } + } return cb.getDataCallbackPos() - prev; } // Everything else uses the socket directly From 8830a876422b266dabdb6b1379044a0678b46c0b Mon Sep 17 00:00:00 2001 From: Marco van Dijk Date: Mon, 26 Aug 2024 16:58:52 +0200 Subject: [PATCH 03/10] Fixes to SegmentReader: do not call `tsStream.finish()` early, fix stopping early --- lib/segmentreader.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/segmentreader.cpp b/lib/segmentreader.cpp index 2669a8af..d2a341f0 100644 --- a/lib/segmentreader.cpp +++ b/lib/segmentreader.cpp @@ -142,8 +142,8 @@ namespace Mist{ } if (parser == STRM_TS){ - if (currBuf->size() == currBuf->rsize()){tsStream.finish();} - if (tsStream.hasPacketOnEachTrack() || currBuf->size() == currBuf->rsize()){ + if (currBuf->size() == currBuf->rsize() && offset + 188 > currBuf->size()){tsStream.finish();} + if (tsStream.hasPacketOnEachTrack() || (currBuf->size() == currBuf->rsize() && offset + 188 > currBuf->size())){ if (!tsStream.hasPacket()){return false;} tsStream.getEarliestPacket(thisPacket); return true; From ee853b1ffbb409f85541b594ef815b8e11663967 Mon Sep 17 00:00:00 2001 From: Marco van Dijk Date: Thu, 19 Sep 2024 16:21:47 +0200 Subject: [PATCH 04/10] Change Input::keepRunning to be more generic, override it in MistInBuffer and MistInHLS --- src/input/input.cpp | 16 ++++++++-------- src/input/input.h | 2 +- src/input/input_buffer.cpp | 8 ++++++++ src/input/input_buffer.h | 1 + src/input/input_hls.cpp | 10 ++++++++++ src/input/input_hls.h | 1 + 6 files changed, 29 insertions(+), 9 deletions(-) diff --git a/src/input/input.cpp b/src/input/input.cpp index 368e6066..f2bb0a30 100644 --- a/src/input/input.cpp +++ b/src/input/input.cpp @@ -894,13 +894,6 @@ namespace Mist{ break; } - if (M.getLive() && !internalOnly){ - uint64_t currLastUpdate = M.getLastUpdated(); - if (currLastUpdate > activityCounter){activityCounter = currLastUpdate;} - }else{ - if ((connectedUsers || isAlwaysOn()) && M.getValidTracks().size()){activityCounter = Util::bootSecs();} - } - inputServeStats(); // if not shutting down, wait 1 second before looping preMs = Util::bootMS() - preMs; @@ -944,7 +937,14 @@ namespace Mist{ /// For live streams, this is twice the biggest fragment duration. /// For non-live streams this is INPUT_TIMEOUT seconds. /// The default Pro implementation also allows cancelling the shutdown through the STREAM_UNLOAD trigger. - bool Input::keepRunning(){ + bool Input::keepRunning(bool updateActCtr){ + // Default behaviour to stop an input when there's not activity after X seconds + // This is overriden by certain inputs (buffer, HLS, more) where there is different logic + // for updating 'activityCounter' + if (updateActCtr){ + if ((connectedUsers || isAlwaysOn()) && M.getValidTracks().size()){activityCounter = Util::bootSecs();} + } + // We keep running in serve mode if the config is still active AND either // - INPUT_TIMEOUT seconds haven't passed yet, // - this is a live stream and at least two of the biggest fragment haven't passed yet, diff --git a/src/input/input.h b/src/input/input.h index c7d57de0..c4f6bb0b 100644 --- a/src/input/input.h +++ b/src/input/input.h @@ -74,7 +74,7 @@ namespace Mist{ virtual void getNext(size_t idx = INVALID_TRACK_ID){} virtual void seek(uint64_t seekTime, size_t idx = INVALID_TRACK_ID){} virtual void finish(); - virtual bool keepRunning(); + virtual bool keepRunning(bool updateActCtr = true); virtual bool openStreamSource(){return readHeader();} virtual void closeStreamSource(){} virtual void parseStreamHeader(){} diff --git a/src/input/input_buffer.cpp b/src/input/input_buffer.cpp index 9eac0ef8..45ac78a4 100644 --- a/src/input/input_buffer.cpp +++ b/src/input/input_buffer.cpp @@ -258,6 +258,14 @@ namespace Mist{ meta.setLive(true); } + bool InputBuffer::keepRunning(bool updateActCtr){ + if (M.getLive()){ + uint64_t currLastUpdate = M.getLastUpdated(); + if (currLastUpdate > activityCounter){activityCounter = currLastUpdate;} + } + return Input::keepRunning(false); + } + /// Checks if removing a key from this track is allowed/safe, and if so, removes it. /// Returns true if a key was actually removed, false otherwise /// Aborts if any of the following conditions are true (while active): diff --git a/src/input/input_buffer.h b/src/input/input_buffer.h index 52c2fd88..adb4a687 100644 --- a/src/input/input_buffer.h +++ b/src/input/input_buffer.h @@ -34,6 +34,7 @@ namespace Mist{ bool needHeader(){return false;} void getNext(size_t idx = INVALID_TRACK_ID){}; void seek(uint64_t seekTime, size_t idx = INVALID_TRACK_ID){}; + bool keepRunning(bool updateActCtr = true); void removeTrack(size_t tid); diff --git a/src/input/input_hls.cpp b/src/input/input_hls.cpp index e1e7cb04..4cc7ad82 100644 --- a/src/input/input_hls.cpp +++ b/src/input/input_hls.cpp @@ -1169,6 +1169,16 @@ namespace Mist{ } } + bool InputHLS::keepRunning(bool updateActCtr){ + if (isAlwaysOn()){ + uint64_t currLastUpdate = M.getLastUpdated(); + if (currLastUpdate > activityCounter){activityCounter = currLastUpdate;} + return Input::keepRunning(false); + }else{ + return Input::keepRunning(true); + } + } + /// \brief Applies any offset to the packets original timestamp /// \param packetTime: the original timestamp of the packet /// \param tid: the trackid corresponding to this track and playlist diff --git a/src/input/input_hls.h b/src/input/input_hls.h index 78b4aa75..bccb24a9 100644 --- a/src/input/input_hls.h +++ b/src/input/input_hls.h @@ -147,6 +147,7 @@ namespace Mist{ void postHeader(); void getNext(size_t idx = INVALID_TRACK_ID); void seek(uint64_t seekTime, size_t idx = INVALID_TRACK_ID); + bool keepRunning(bool updateActCtr = true); bool readIndex(); bool initPlaylist(const std::string &uri, bool fullInit = true); From 3d62b9b35dfdd9e0e6a9a359a87345c4ed0381bf Mon Sep 17 00:00:00 2001 From: Thulinma Date: Tue, 24 Sep 2024 15:28:36 +0200 Subject: [PATCH 05/10] Prevent AAC and FLV input keepRunning overrides from hiding generic functions --- src/input/input_aac.cpp | 4 ++-- src/input/input_aac.h | 2 +- src/input/input_flv.cpp | 4 ++-- src/input/input_flv.h | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/input/input_aac.cpp b/src/input/input_aac.cpp index efb65ea1..6800352b 100644 --- a/src/input/input_aac.cpp +++ b/src/input/input_aac.cpp @@ -111,7 +111,7 @@ namespace Mist{ // Overrides the default keepRunning function to shut down // if the file disappears or changes, by polling the file's mtime. // If neither applies, calls the original function. - bool InputAAC::keepRunning(){ + bool InputAAC::keepRunning(bool updateActCtr){ struct stat statData; if (stat(config->getString("input").c_str(), &statData) == -1){ INFO_MSG("Shutting down because input file disappeared"); @@ -121,7 +121,7 @@ namespace Mist{ INFO_MSG("Shutting down because input file changed"); return false; } - return Input::keepRunning(); + return Input::keepRunning(updateActCtr); } diff --git a/src/input/input_aac.h b/src/input/input_aac.h index ce8bd812..9b7ad54a 100644 --- a/src/input/input_aac.h +++ b/src/input/input_aac.h @@ -16,7 +16,7 @@ namespace Mist{ bool readHeader(); void seek(uint64_t seekTime, size_t idx); void getNext(size_t idx = INVALID_TRACK_ID); - bool keepRunning(); + bool keepRunning(bool updateActCtr = true); uint64_t lastModTime; HTTP::URIReader inFile; size_t audioTrack; diff --git a/src/input/input_flv.cpp b/src/input/input_flv.cpp index 148d3555..85a3c35f 100644 --- a/src/input/input_flv.cpp +++ b/src/input/input_flv.cpp @@ -67,7 +67,7 @@ namespace Mist{ /// Overrides the default keepRunning function to shut down /// if the file disappears or changes, by polling the file's mtime. /// If neither applies, calls the original function. - bool InputFLV::keepRunning(){ + bool InputFLV::keepRunning(bool updateActCtr){ struct stat statData; if (stat(config->getString("input").c_str(), &statData) == -1){ INFO_MSG("Shutting down because input file disappeared"); @@ -77,7 +77,7 @@ namespace Mist{ INFO_MSG("Shutting down because input file changed"); return false; } - return Input::keepRunning(); + return Input::keepRunning(updateActCtr); } bool InputFLV::readHeader(){ diff --git a/src/input/input_flv.h b/src/input/input_flv.h index 3a455feb..634194b7 100644 --- a/src/input/input_flv.h +++ b/src/input/input_flv.h @@ -15,7 +15,7 @@ namespace Mist{ bool readHeader(); void getNext(size_t idx = INVALID_TRACK_ID); void seek(uint64_t seekTime, size_t idx = INVALID_TRACK_ID); - bool keepRunning(); + bool keepRunning(bool updateActCtr = true); FLV::Tag tmpTag; uint64_t lastModTime; FILE *inFile; From 9f18c39ada12d5f933114964f37aae0636ec18b4 Mon Sep 17 00:00:00 2001 From: Thulinma Date: Thu, 4 May 2023 12:12:30 +0200 Subject: [PATCH 06/10] Rewrite SRT implementation to use generic UDP sockets for listeners, then only init the SRT library post-fork so that we can use a process per connection like everywhere else --- lib/auth.cpp | 2 +- lib/config.cpp | 16 +- lib/config.h | 1 + lib/socket.cpp | 483 ++++++++++++++--------- lib/socket.h | 23 +- lib/socket_srt.cpp | 281 +++++++------ lib/socket_srt.h | 7 +- lib/util.cpp | 35 +- lib/util.h | 3 + meson.build | 3 + src/controller/controller_statistics.cpp | 90 +++-- src/input/input_tssrt.cpp | 123 +++--- src/input/input_tssrt.h | 6 +- src/output/meson.build | 2 +- src/output/output.cpp | 1 + src/output/output_ts.cpp | 11 +- src/output/output_ts.h | 2 +- src/output/output_ts_base.cpp | 3 + src/output/output_tssrt.cpp | 408 ++++++++++--------- src/output/output_tssrt.h | 15 +- 20 files changed, 903 insertions(+), 612 deletions(-) diff --git a/lib/auth.cpp b/lib/auth.cpp index 468cd381..276fc5db 100644 --- a/lib/auth.cpp +++ b/lib/auth.cpp @@ -213,7 +213,7 @@ namespace Secure{ } /// Calculates a SHA256 digest as per NSAs SHA-2, returning it as binary. - /// Assumes output is big enough to contain 16 bytes of data. + /// Assumes output is big enough to contain 32 bytes of data. void sha256bin(const char *input, const unsigned int in_len, char *output){ // Initialize the hash, according to MD5 spec. uint32_t hash[] ={0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, diff --git a/lib/config.cpp b/lib/config.cpp index 361e33c3..74c4ccc4 100644 --- a/lib/config.cpp +++ b/lib/config.cpp @@ -36,7 +36,7 @@ bool Util::Config::is_active = false; bool Util::Config::is_restarting = false; -static Socket::Server *serv_sock_pointer = 0; +static int serv_sock_fd = -1; uint32_t Util::printDebugLevel = DEBUG; __thread char Util::streamName[256] = {0}; __thread char Util::exitReason[256] = {0}; @@ -521,7 +521,7 @@ int Util::Config::serveThreadedSocket(int (*callback)(Socket::Connection &)){ return 1; } Socket::getSocketName(server_socket.getSocket(), Util::listenInterface, Util::listenPort); - serv_sock_pointer = &server_socket; + serv_sock_fd = server_socket.getSocket(); activate(); if (server_socket.getSocket()){ int oldSock = server_socket.getSocket(); @@ -531,7 +531,7 @@ int Util::Config::serveThreadedSocket(int (*callback)(Socket::Connection &)){ } } int r = threadServer(server_socket, callback); - serv_sock_pointer = 0; + serv_sock_fd = -1; return r; } @@ -549,7 +549,7 @@ int Util::Config::serveForkedSocket(int (*callback)(Socket::Connection &S)){ return 1; } Socket::getSocketName(server_socket.getSocket(), Util::listenInterface, Util::listenPort); - serv_sock_pointer = &server_socket; + serv_sock_fd = server_socket.getSocket(); activate(); if (server_socket.getSocket()){ int oldSock = server_socket.getSocket(); @@ -559,7 +559,7 @@ int Util::Config::serveForkedSocket(int (*callback)(Socket::Connection &S)){ } } int r = forkServer(server_socket, callback); - serv_sock_pointer = 0; + serv_sock_fd = -1; return r; } @@ -601,6 +601,10 @@ void Util::Config::setMutexAborter(void * mutex){ mutabort = (tthread::mutex*)mutex; } +void Util::Config::setServerFD(int fd){ + serv_sock_fd = fd; +} + /// Basic signal handler. Sets is_active to false if it receives /// a SIGINT, SIGHUP or SIGTERM signal, reaps children for the SIGCHLD /// signal, and ignores all other signals. @@ -610,7 +614,7 @@ void Util::Config::signal_handler(int signum, siginfo_t *sigInfo, void *ignore){ case SIGHUP: case SIGTERM: if (!mutabort || mutabort->try_lock()){ - if (serv_sock_pointer){serv_sock_pointer->close();} + if (serv_sock_fd != -1){close(serv_sock_fd);} if (mutabort){mutabort->unlock();} } #if DEBUG >= DLVL_DEVEL diff --git a/lib/config.h b/lib/config.h index a22967b6..f23b598d 100644 --- a/lib/config.h +++ b/lib/config.h @@ -38,6 +38,7 @@ namespace Util{ public: static void setMutexAborter(void * mutex); static void wipeShm(); + static void setServerFD(int fd); // variables static bool is_active; ///< Set to true by activate(), set to false by the signal handler. static bool is_restarting; ///< Set to true when restarting, set to false on boot. diff --git a/lib/socket.cpp b/lib/socket.cpp index f1df95d7..3e2104c8 100644 --- a/lib/socket.cpp +++ b/lib/socket.cpp @@ -44,6 +44,14 @@ static const char *gai_strmagic(int errcode){ } } +std::string Socket::sockaddrToString(const sockaddr* A){ + char addressBuffer[INET6_ADDRSTRLEN]; + if (inet_ntop(AF_INET, A, addressBuffer, INET6_ADDRSTRLEN)){ + return addressBuffer; + } + return ""; +} + static std::string getIPv6BinAddr(const struct sockaddr_in6 &remoteaddr){ char tmpBuffer[17] = "\000\000\000\000\000\000\000\000\000\000\377\377\000\000\000\000"; switch (remoteaddr.sin6_family){ @@ -130,6 +138,44 @@ bool Socket::matchIPv6Addr(const std::string &A, const std::string &B, uint8_t p return true; } +bool Socket::compareAddress(const sockaddr* A, const sockaddr* B){ + if (!A || !B){return false;} + bool aSix = false, bSix = false; + char *aPtr = 0, *bPtr = 0; + uint16_t aPort = 0, bPort = 0; + if (A->sa_family == AF_INET){ + aPtr = (char*)&((sockaddr_in*)A)->sin_addr; + aPort = ((sockaddr_in*)A)->sin_port; + }else if(A->sa_family == AF_INET6){ + aPtr = (char*)&((sockaddr_in6*)A)->sin6_addr; + aPort = ((sockaddr_in6*)A)->sin6_port; + if (!memcmp("\000\000\000\000\000\000\000\000\000\000\377\377", aPtr, 12)){ + aPtr += 12; + }else{ + aSix = true; + } + }else{ + return false; + } + if (B->sa_family == AF_INET){ + bPtr = (char*)&((sockaddr_in*)B)->sin_addr; + bPort = ((sockaddr_in*)B)->sin_port; + }else if(B->sa_family == AF_INET6){ + bPtr = (char*)&((sockaddr_in6*)B)->sin6_addr; + bPort = ((sockaddr_in6*)B)->sin6_port; + if (!memcmp("\000\000\000\000\000\000\000\000\000\000\377\377", bPtr, 12)){ + bPtr += 12; + }else{ + bSix = true; + } + }else{ + return false; + } + if (aPort != bPort){return false;} + if (aSix != bSix){return false;} + return !memcmp(aPtr, bPtr, aSix?16:4); +} + /// Attempts to match the given address with optional subnet to the given binary-form IPv6 address. /// Returns true if match could be made, false otherwise. bool Socket::isBinAddress(const std::string &binAddr, std::string addr){ @@ -180,7 +226,7 @@ std::string Socket::getBinForms(std::string addr){ memset(&hints, 0, sizeof(struct addrinfo)); hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM; - hints.ai_flags = AI_ADDRCONFIG | AI_V4MAPPED; + hints.ai_flags = AI_ADDRCONFIG | AI_V4MAPPED | AI_ALL; hints.ai_protocol = 0; hints.ai_canonname = NULL; hints.ai_addr = NULL; @@ -200,6 +246,45 @@ std::string Socket::getBinForms(std::string addr){ return ret; } +std::deque Socket::getAddrs(std::string addr, uint16_t port, int family){ + std::deque ret; + struct addrinfo *result, *rp, hints; + if (addr.substr(0, 7) == "::ffff:"){addr = addr.substr(7);} + std::stringstream ss; + ss << port; + + memset(&hints, 0, sizeof(struct addrinfo)); + // For unspecified, we do IPv6, then do IPv4 separately after + hints.ai_family = family==AF_UNSPEC?AF_INET6:family; + hints.ai_socktype = SOCK_DGRAM; + hints.ai_flags = AI_ADDRCONFIG | AI_PASSIVE | AI_V4MAPPED | AI_ALL; + hints.ai_protocol = IPPROTO_UDP; + int s = getaddrinfo(addr.c_str(), ss.str().c_str(), &hints, &result); + if (!s){ + // Store each address in a string and put it in the deque. + for (rp = result; rp != NULL; rp = rp->ai_next){ + ret.push_back(std::string((char*)rp->ai_addr, rp->ai_addrlen)); + } + freeaddrinfo(result); + } + + // If failed or unspecified, (also) try IPv4 + if (s || family==AF_UNSPEC){ + hints.ai_family = AF_INET; + s = getaddrinfo(addr.c_str(), ss.str().c_str(), &hints, &result); + if (!s){ + // Store each address in a string and put it in the deque. + for (rp = result; rp != NULL; rp = rp->ai_next){ + ret.push_back(std::string((char*)rp->ai_addr, rp->ai_addrlen)); + } + freeaddrinfo(result); + } + } + + // Return all we found + return ret; +} + /// Checks bytes (length len) containing a binary-encoded IPv4 or IPv6 IP address, and writes it in /// human-readable notation to target. Writes "unknown" if it cannot decode to a sensible value. void Socket::hostBytesToStr(const char *bytes, size_t len, std::string &target){ @@ -1679,6 +1764,7 @@ void Socket::UDPConnection::init(bool _nonblock, int _family){ isConnected = false; wasEncrypted = false; pretendReceive = false; + ignoreSendErrors = false; sock = socket(family, SOCK_DGRAM, 0); if (sock == -1 && family == AF_INET6){ sock = socket(AF_INET, SOCK_DGRAM, 0); @@ -1700,10 +1786,6 @@ void Socket::UDPConnection::init(bool _nonblock, int _family){ up = 0; down = 0; - destAddr = 0; - destAddr_size = 0; - recvAddr = 0; - recvAddr_size = 0; hasReceiveData = false; #ifdef __CYGWIN__ data.allocate(SOCKETSIZE); @@ -1712,6 +1794,23 @@ void Socket::UDPConnection::init(bool _nonblock, int _family){ #endif } +void Socket::UDPConnection::assimilate(int _sock){ + if (sock != -1){close();} + sock = _sock; + { // Extract socket family + struct sockaddr_storage fin_addr; + socklen_t alen = sizeof(fin_addr); + if (getsockname(sock, (struct sockaddr *)&fin_addr, &alen) == 0){ + family = fin_addr.ss_family; + if (family == AF_INET6){ + boundPort = ntohs(((struct sockaddr_in6 *)&fin_addr)->sin6_port); + }else{ + boundPort = ntohs(((struct sockaddr_in *)&fin_addr)->sin_port); + } + } + } +} + #if HAVE_UPSTREAM_MBEDTLS_SRTP #if MBEDTLS_VERSION_MAJOR > 2 static void dtlsExtractKeyData( void *user, mbedtls_ssl_key_export_type type, const unsigned char *ms, size_t, const unsigned char client_random[32], const unsigned char server_random[32], mbedtls_tls_prf_types tls_prf_type){ @@ -1927,18 +2026,10 @@ void Socket::UDPConnection::checkRecvBuf(){ Socket::UDPConnection::UDPConnection(const UDPConnection &o){ init(!o.isBlocking, o.family); INFO_MSG("Copied socket of type %s", addrFam(o.family)); - if (o.destAddr && o.destAddr_size){ - destAddr = malloc(o.destAddr_size); - destAddr_size = o.destAddr_size; - if (destAddr){memcpy(destAddr, o.destAddr, o.destAddr_size);} - } - if (o.recvAddr && o.recvAddr_size){ - recvAddr = malloc(o.recvAddr_size); - recvAddr_size = o.recvAddr_size; - if (recvAddr){memcpy(recvAddr, o.recvAddr, o.recvAddr_size);} - } + if (o.destAddr.size()){destAddr = o.destAddr;} + if (o.recvAddr.size()){recvAddr = o.recvAddr;} if (o.data.size()){ - data.assign(o.data, o.data.size()); + data = o.data; pretendReceive = true; } hasReceiveData = o.hasReceiveData; @@ -1956,14 +2047,6 @@ void Socket::UDPConnection::close(){ /// Closes the UDP socket, cleans up any memory allocated by the socket. Socket::UDPConnection::~UDPConnection(){ close(); - if (destAddr){ - free(destAddr); - destAddr = 0; - } - if (recvAddr){ - free(recvAddr); - recvAddr = 0; - } #ifdef SSL deinitDTLS(); #endif @@ -1975,10 +2058,10 @@ bool Socket::UDPConnection::operator==(const Socket::UDPConnection& b) const{ if (sock == b.sock){return true;} // If either is closed (and the other is not), not equal. if (sock == -1 || b.sock == -1){return false;} - size_t recvSize = recvAddr_size; - if (b.recvAddr_size < recvSize){recvSize = b.recvAddr_size;} - size_t destSize = destAddr_size; - if (b.destAddr_size < destSize){destSize = b.destAddr_size;} + size_t recvSize = recvAddr.size(); + if (b.recvAddr.size() < recvSize){recvSize = b.recvAddr.size();} + size_t destSize = destAddr.size(); + if (b.destAddr.size() < destSize){destSize = b.destAddr.size();} // They are equal if they hold the same local and remote address. if (recvSize && destSize && destAddr && b.destAddr && recvAddr && b.recvAddr){ if (!memcmp(recvAddr, b.recvAddr, recvSize) && !memcmp(destAddr, b.destAddr, destSize)){ @@ -1999,128 +2082,108 @@ void Socket::UDPConnection::setSocketFamily(int AF_TYPE){\ /// Allocates enough space for the largest type of address we support, so that receive calls can write to it. void Socket::UDPConnection::allocateDestination(){ - if (destAddr && destAddr_size < sizeof(sockaddr_in6)){ - free(destAddr); - destAddr = 0; + if (!destAddr.size()){ + destAddr.truncate(0); + destAddr.allocate(sizeof(sockaddr_in6)); + memset(destAddr, 0, sizeof(sockaddr_in6)); + ((struct sockaddr *)(char*)destAddr)->sa_family = AF_UNSPEC; + destAddr.append(0, sizeof(sockaddr_in6)); } - if (!destAddr){ - destAddr = malloc(sizeof(sockaddr_in6)); - if (destAddr){ - destAddr_size = sizeof(sockaddr_in6); - memset(destAddr, 0, sizeof(sockaddr_in6)); - ((struct sockaddr_in *)destAddr)->sin_family = AF_UNSPEC; - } - } - if (recvAddr && recvAddr_size < sizeof(sockaddr_in6)){ - free(recvAddr); - recvAddr = 0; - } - if (!recvAddr){ - recvAddr = malloc(sizeof(sockaddr_in6)); - if (recvAddr){ - recvAddr_size = sizeof(sockaddr_in6); - memset(recvAddr, 0, sizeof(sockaddr_in6)); - ((struct sockaddr_in *)recvAddr)->sin_family = AF_UNSPEC; - } - const int opt = 1; - if (setsockopt(sock, IPPROTO_IP, IP_PKTINFO, &opt, sizeof(opt))){ - WARN_MSG("Could not set PKTINFO to 1!"); + if (!recvAddr.size()){ + recvAddr.truncate(0); + recvAddr.allocate(sizeof(sockaddr_in6)); + memset(recvAddr, 0, sizeof(sockaddr_in6)); + ((struct sockaddr *)(char*)recvAddr)->sa_family = AF_UNSPEC; + recvAddr.append(0, sizeof(sockaddr_in6)); + } +#ifdef HASPKTINFO + const int opt = 1; + if (setsockopt(sock, IPPROTO_IP, IP_PKTINFO, &opt, sizeof(opt))){ + WARN_MSG("Could not set IPv4 packet info receiving enabled!"); + } + if (family == AF_INET6){ + if (setsockopt(sock, IPPROTO_IPV6, IPV6_RECVPKTINFO, &opt, sizeof(opt))){ + WARN_MSG("Could not set IPv6 packet info receiving enabled!"); } } +#endif } /// Stores the properties of the receiving end of this UDP socket. /// This will be the receiving end for all SendNow calls. void Socket::UDPConnection::SetDestination(std::string destIp, uint32_t port){ DONTEVEN_MSG("Setting destination to %s:%u", destIp.c_str(), port); - // UDP sockets can switch between IPv4 and IPv6 on demand. - // We change IPv4-mapped IPv6 addresses into IPv4 addresses for Windows-sillyness reasons. - if (destIp.substr(0, 7) == "::ffff:"){destIp = destIp.substr(7);} - struct addrinfo *result, *rp, hints; - std::stringstream ss; - ss << port; - memset(&hints, 0, sizeof(struct addrinfo)); - hints.ai_family = family; - hints.ai_socktype = SOCK_DGRAM; - hints.ai_flags = AI_ADDRCONFIG | AI_ALL; - hints.ai_protocol = IPPROTO_UDP; - hints.ai_canonname = NULL; - hints.ai_addr = NULL; - hints.ai_next = NULL; - int s = getaddrinfo(destIp.c_str(), ss.str().c_str(), &hints, &result); - if (s != 0){ - hints.ai_family = AF_UNSPEC; - s = getaddrinfo(destIp.c_str(), ss.str().c_str(), &hints, &result); - if (s != 0){ - FAIL_MSG("Could not connect UDP socket to %s:%i! Error: %s", destIp.c_str(), port, gai_strmagic(s)); - return; - } + std::deque addrs = getAddrs(destIp, port, family); + for (std::deque::iterator it = addrs.begin(); it != addrs.end(); ++it){ + if (setDestination((sockaddr*)it->data(), it->size())){return;} } - - for (rp = result; rp != NULL; rp = rp->ai_next){ - // assume success - if (destAddr){ - free(destAddr); - destAddr = 0; - } - destAddr_size = rp->ai_addrlen; - destAddr = malloc(destAddr_size); - if (!destAddr){return;} - memcpy(destAddr, rp->ai_addr, rp->ai_addrlen); - if (family != rp->ai_family){ - INFO_MSG("Switching UDP socket from %s to %s", addrFam(family), addrFam(rp->ai_family)); - close(); - family = rp->ai_family; - sock = socket(family, SOCK_DGRAM, 0); - { - // Allow address re-use - int on = 1; - setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)); - } - checkRecvBuf(); - if (boundPort){ - INFO_MSG("Rebinding to %s:%d %s", boundAddr.c_str(), boundPort, boundMulti.c_str()); - bind(boundPort, boundAddr, boundMulti); - } - } - { - std::string trueDest; - uint32_t truePort; - GetDestination(trueDest, truePort); - HIGH_MSG("Set UDP destination: %s:%d => %s:%d (%s)", destIp.c_str(), port, trueDest.c_str(), truePort, addrFam(family)); - } - freeaddrinfo(result); - return; - //\todo Possibly detect and handle failure - } - freeaddrinfo(result); - free(destAddr); - destAddr = 0; + destAddr.truncate(0); + allocateDestination(); FAIL_MSG("Could not set destination for UDP socket: %s:%d", destIp.c_str(), port); }// Socket::UDPConnection SetDestination +bool Socket::UDPConnection::setDestination(sockaddr * addr, size_t size){ + // UDP sockets can on-the-fly switch between IPv4/IPv6 if necessary + if (family != addr->sa_family){ + if (ignoreSendErrors){return false;} + WARN_MSG("Switching UDP socket from %s to %s", addrFam(family), addrFam(((sockaddr*)(char*)destAddr)->sa_family)); + close(); + family = addr->sa_family; + sock = socket(family, SOCK_DGRAM, 0); + { + // Allow address re-use + int on = 1; + setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)); + } + if (family == AF_INET6){ + const int optval = 0; + if (setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, &optval, sizeof(optval)) < 0){ + WARN_MSG("Could not set IPv6 UDP socket to be dual-stack! %s", strerror(errno)); + } + } + checkRecvBuf(); + if (boundPort){ + INFO_MSG("Rebinding to %s:%d %s", boundAddr.c_str(), boundPort, boundMulti.c_str()); + bind(boundPort, boundAddr, boundMulti); + } + } + hasReceiveData = false; + destAddr.assign(addr, size); + { + std::string trueDest; + uint32_t truePort; + GetDestination(trueDest, truePort); + HIGH_MSG("Set UDP destination to %s:%d (%s)", trueDest.c_str(), truePort, addrFam(family)); + } + return true; +} + +const Util::ResizeablePointer & Socket::UDPConnection::getRemoteAddr() const{ + return destAddr; +} + /// Gets the properties of the receiving end of this UDP socket. /// This will be the receiving end for all SendNow calls. void Socket::UDPConnection::GetDestination(std::string &destIp, uint32_t &port){ - if (!destAddr || !destAddr_size){ + if (!destAddr.size()){ destIp = ""; port = 0; return; } char addr_str[INET6_ADDRSTRLEN + 1]; addr_str[INET6_ADDRSTRLEN] = 0; // set last byte to zero, to prevent walking out of the array - if (((struct sockaddr_in *)destAddr)->sin_family == AF_INET6){ - if (inet_ntop(AF_INET6, &(((struct sockaddr_in6 *)destAddr)->sin6_addr), addr_str, INET6_ADDRSTRLEN) != 0){ + if (((struct sockaddr *)(char*)destAddr)->sa_family == AF_INET6){ + if (inet_ntop(AF_INET6, &(((struct sockaddr_in6 *)(char*)destAddr)->sin6_addr), addr_str, INET6_ADDRSTRLEN) != 0){ destIp = addr_str; - port = ntohs(((struct sockaddr_in6 *)destAddr)->sin6_port); + port = ntohs(((struct sockaddr_in6 *)(char*)destAddr)->sin6_port); return; } } - if (((struct sockaddr_in *)destAddr)->sin_family == AF_INET){ - if (inet_ntop(AF_INET, &(((struct sockaddr_in *)destAddr)->sin_addr), addr_str, INET6_ADDRSTRLEN) != 0){ + if (((struct sockaddr_in *)(char*)destAddr)->sin_family == AF_INET){ + if (inet_ntop(AF_INET, &(((struct sockaddr_in *)(char*)destAddr)->sin_addr), addr_str, INET6_ADDRSTRLEN) != 0){ destIp = addr_str; - port = ntohs(((struct sockaddr_in *)destAddr)->sin_port); + port = ntohs(((struct sockaddr_in *)(char*)destAddr)->sin_port); return; } } @@ -2132,24 +2195,24 @@ void Socket::UDPConnection::GetDestination(std::string &destIp, uint32_t &port){ /// Gets the properties of the receiving end of the local UDP socket. /// This will be the sending end for all SendNow calls. void Socket::UDPConnection::GetLocalDestination(std::string &destIp, uint32_t &port){ - if (!recvAddr || !recvAddr_size){ + if (!recvAddr.size()){ destIp = ""; port = 0; return; } char addr_str[INET6_ADDRSTRLEN + 1]; addr_str[INET6_ADDRSTRLEN] = 0; // set last byte to zero, to prevent walking out of the array - if (((struct sockaddr_in *)recvAddr)->sin_family == AF_INET6){ - if (inet_ntop(AF_INET6, &(((struct sockaddr_in6 *)recvAddr)->sin6_addr), addr_str, INET6_ADDRSTRLEN) != 0){ + if (((struct sockaddr *)(char*)recvAddr)->sa_family == AF_INET6){ + if (inet_ntop(AF_INET6, &(((struct sockaddr_in6 *)(char*)recvAddr)->sin6_addr), addr_str, INET6_ADDRSTRLEN) != 0){ destIp = addr_str; - port = ntohs(((struct sockaddr_in6 *)recvAddr)->sin6_port); + port = ntohs(((struct sockaddr_in6 *)(char*)recvAddr)->sin6_port); return; } } - if (((struct sockaddr_in *)recvAddr)->sin_family == AF_INET){ - if (inet_ntop(AF_INET, &(((struct sockaddr_in *)recvAddr)->sin_addr), addr_str, INET6_ADDRSTRLEN) != 0){ + if (((struct sockaddr *)(char*)recvAddr)->sa_family == AF_INET){ + if (inet_ntop(AF_INET, &(((struct sockaddr_in *)(char*)recvAddr)->sin_addr), addr_str, INET6_ADDRSTRLEN) != 0){ destIp = addr_str; - port = ntohs(((struct sockaddr_in *)recvAddr)->sin_port); + port = ntohs(((struct sockaddr_in *)(char*)recvAddr)->sin_port); return; } } @@ -2162,7 +2225,7 @@ void Socket::UDPConnection::GetLocalDestination(std::string &destIp, uint32_t &p /// This will be the receiving end for all SendNow calls. std::string Socket::UDPConnection::getBinDestination(){ std::string binList; - if (destAddr && destAddr_size){binList = getIPv6BinAddr(*(sockaddr_in6*)destAddr);} + if (destAddr.size()){binList = getIPv6BinAddr(*(sockaddr_in6*)(char*)destAddr);} if (binList.size() < 16){return std::string("\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000", 16);} return binList.substr(0, 16); }// Socket::UDPConnection GetDestination @@ -2170,12 +2233,12 @@ std::string Socket::UDPConnection::getBinDestination(){ /// Returns the port number of the receiving end of this socket. /// Returns 0 on error. uint32_t Socket::UDPConnection::getDestPort() const{ - if (!destAddr || !destAddr_size){return 0;} - if (((struct sockaddr_in *)destAddr)->sin_family == AF_INET6){ - return ntohs(((struct sockaddr_in6 *)destAddr)->sin6_port); + if (!destAddr.size()){return 0;} + if (((const struct sockaddr *)(const char*)destAddr)->sa_family == AF_INET6){ + return ntohs(((const struct sockaddr_in6 *)(const char*)destAddr)->sin6_port); } - if (((struct sockaddr_in *)destAddr)->sin_family == AF_INET){ - return ntohs(((struct sockaddr_in *)destAddr)->sin_port); + if (((const struct sockaddr *)(const char*)destAddr)->sa_family == AF_INET){ + return ntohs(((const struct sockaddr_in *)(const char*)destAddr)->sin_port); } return 0; } @@ -2189,6 +2252,10 @@ void Socket::UDPConnection::setBlocking(bool blocking){ } } +void Socket::UDPConnection::setIgnoreSendErrors(bool ign){ + ignoreSendErrors = ign; +} + /// Sends a UDP datagram using the buffer sdata. /// This function simply calls SendNow(const char*, size_t) void Socket::UDPConnection::SendNow(const std::string &sdata){ @@ -2207,7 +2274,7 @@ void Socket::UDPConnection::SendNow(const char *sdata){ /// Does not do anything if len < 1. /// Prints an DLVL_FAIL level debug message if sending failed. void Socket::UDPConnection::SendNow(const char *sdata, size_t len){ - SendNow(sdata, len, (sockaddr*)destAddr, destAddr_size); + SendNow(sdata, len, (sockaddr*)(char*)destAddr, destAddr.size()); } /// Sends a UDP datagram using the buffer sdata of length len. @@ -2220,6 +2287,7 @@ void Socket::UDPConnection::SendNow(const char *sdata, size_t len, sockaddr * dA if (r > 0){ up += r; }else{ + if (ignoreSendErrors){return;} if (errno == EDESTADDRREQ){ close(); return; @@ -2228,8 +2296,8 @@ void Socket::UDPConnection::SendNow(const char *sdata, size_t len, sockaddr * dA } return; } -#if !defined(__CYGWIN__) && !defined(_WIN32) - if (hasReceiveData && recvAddr){ +#ifdef HASPKTINFO + if (hasReceiveData && recvAddr.size()){ msghdr mHdr; char msg_control[0x100]; iovec iovec; @@ -2244,22 +2312,34 @@ void Socket::UDPConnection::SendNow(const char *sdata, size_t len, sockaddr * dA mHdr.msg_flags = 0; int cmsg_space = 0; cmsghdr * cmsg = CMSG_FIRSTHDR(&mHdr); - cmsg->cmsg_level = IPPROTO_IP; - cmsg->cmsg_type = IP_PKTINFO; + if (family == AF_INET){ + cmsg->cmsg_level = IPPROTO_IP; + cmsg->cmsg_type = IP_PKTINFO; - struct in_pktinfo in_pktinfo; - memcpy(&(in_pktinfo.ipi_spec_dst), &(((sockaddr_in*)recvAddr)->sin_family), sizeof(in_pktinfo.ipi_spec_dst)); - in_pktinfo.ipi_ifindex = recvInterface; - cmsg->cmsg_len = CMSG_LEN(sizeof(in_pktinfo)); - *(struct in_pktinfo*)CMSG_DATA(cmsg) = in_pktinfo; - cmsg_space += CMSG_SPACE(sizeof(in_pktinfo)); + struct in_pktinfo in_pktinfo; + memcpy(&(in_pktinfo.ipi_spec_dst), &(((sockaddr_in*)(char*)recvAddr)->sin_family), sizeof(in_pktinfo.ipi_spec_dst)); + in_pktinfo.ipi_ifindex = recvInterface; + cmsg->cmsg_len = CMSG_LEN(sizeof(in_pktinfo)); + *(struct in_pktinfo*)CMSG_DATA(cmsg) = in_pktinfo; + cmsg_space += CMSG_SPACE(sizeof(in_pktinfo)); + }else if (family == AF_INET6){ + cmsg->cmsg_level = IPPROTO_IPV6; + cmsg->cmsg_type = IPV6_PKTINFO; + + struct in6_pktinfo in6_pktinfo; + memcpy(&(in6_pktinfo.ipi6_addr), &(((sockaddr_in6*)(char*)recvAddr)->sin6_addr), sizeof(in6_pktinfo.ipi6_addr)); + in6_pktinfo.ipi6_ifindex = recvInterface; + cmsg->cmsg_len = CMSG_LEN(sizeof(in6_pktinfo)); + *(struct in6_pktinfo*)CMSG_DATA(cmsg) = in6_pktinfo; + cmsg_space += CMSG_SPACE(sizeof(in6_pktinfo)); + } mHdr.msg_controllen = cmsg_space; int r = sendmsg(sock, &mHdr, 0); if (r > 0){ up += r; }else{ - if (errno != ENETUNREACH){ + if (errno != ENETUNREACH && !ignoreSendErrors){ FAIL_MSG("Could not send UDP data through %d: %s", sock, strerror(errno)); } } @@ -2270,11 +2350,11 @@ void Socket::UDPConnection::SendNow(const char *sdata, size_t len, sockaddr * dA if (r > 0){ up += r; }else{ - if (errno != ENETUNREACH){ + if (errno != ENETUNREACH && !ignoreSendErrors){ FAIL_MSG("Could not send UDP data through %d: %s", sock, strerror(errno)); } } -#if !defined(__CYGWIN__) && !defined(_WIN32) +#ifdef HASPKTINFO } #endif } @@ -2372,6 +2452,10 @@ std::string Socket::UDPConnection::getBoundAddress(){ return boundaddr; } +uint16_t Socket::UDPConnection::getBoundPort() const{ + return boundPort; +} + /// Bind to a port number, returning the bound port. /// If that fails, returns zero. /// \arg port Port to bind to, required. @@ -2383,13 +2467,15 @@ uint16_t Socket::UDPConnection::bind(int port, std::string iface, const std::str close(); // we open a new socket for each attempt int addr_ret; bool multicast = false; + bool repeatWithIPv4 = false; struct addrinfo hints, *addr_result, *rp; memset(&hints, 0, sizeof(hints)); hints.ai_flags = AI_ADDRCONFIG | AI_PASSIVE | AI_V4MAPPED; - if (destAddr && destAddr_size){ - hints.ai_family = ((struct sockaddr_in *)destAddr)->sin_family; + if (destAddr.size()){ + hints.ai_family = ((struct sockaddr *)(char*)destAddr)->sa_family; }else{ - hints.ai_family = AF_UNSPEC; + hints.ai_family = AF_INET6; + repeatWithIPv4 = true; } hints.ai_socktype = SOCK_DGRAM; @@ -2398,14 +2484,24 @@ uint16_t Socket::UDPConnection::bind(int port, std::string iface, const std::str std::stringstream ss; ss << port; +repeatAddressFinding: + if (iface == "0.0.0.0" || iface.length() == 0){ if ((addr_ret = getaddrinfo(0, ss.str().c_str(), &hints, &addr_result)) != 0){ FAIL_MSG("Could not resolve %s for UDP: %s", iface.c_str(), gai_strmagic(addr_ret)); + if (repeatWithIPv4 && hints.ai_family != AF_INET){ + hints.ai_family = AF_INET; + goto repeatAddressFinding; + } return 0; } }else{ if ((addr_ret = getaddrinfo(iface.c_str(), ss.str().c_str(), &hints, &addr_result)) != 0){ FAIL_MSG("Could not resolve %s for UDP: %s", iface.c_str(), gai_strmagic(addr_ret)); + if (repeatWithIPv4 && hints.ai_family != AF_INET){ + hints.ai_family = AF_INET; + goto repeatAddressFinding; + } return 0; } } @@ -2422,7 +2518,7 @@ uint16_t Socket::UDPConnection::bind(int port, std::string iface, const std::str } if (rp->ai_family == AF_INET6){ const int optval = 0; - if (setsockopt(sock, SOL_SOCKET, IPV6_V6ONLY, &optval, sizeof(optval)) < 0){ + if (setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, &optval, sizeof(optval)) < 0){ WARN_MSG("Could not set IPv6 UDP socket to be dual-stack! %s", strerror(errno)); } } @@ -2483,6 +2579,10 @@ uint16_t Socket::UDPConnection::bind(int port, std::string iface, const std::str freeaddrinfo(addr_result); if (sock == -1){ FAIL_MSG("Could not open %s for UDP: %s", iface.c_str(), err_str.c_str()); + if (repeatWithIPv4 && hints.ai_family != AF_INET){ + hints.ai_family = AF_INET; + goto repeatAddressFinding; + } return 0; } @@ -2570,7 +2670,7 @@ uint16_t Socket::UDPConnection::bind(int port, std::string iface, const std::str } bool Socket::UDPConnection::connect(){ - if (!recvAddr || !recvAddr_size || !destAddr || !destAddr_size){ + if (!recvAddr.size() || !destAddr.size()){ WARN_MSG("Attempting to connect a UDP socket without local and/or remote address!"); return false; } @@ -2579,27 +2679,23 @@ bool Socket::UDPConnection::connect(){ std::string destIp; uint32_t port = 0; char addr_str[INET6_ADDRSTRLEN + 1]; - if (((struct sockaddr_in *)recvAddr)->sin_family == AF_INET6){ - if (inet_ntop(AF_INET6, &(((struct sockaddr_in6 *)recvAddr)->sin6_addr), addr_str, INET6_ADDRSTRLEN) != 0){ + if (((struct sockaddr *)(char*)recvAddr)->sa_family == AF_INET6){ + if (inet_ntop(AF_INET6, &(((struct sockaddr_in6 *)(char*)recvAddr)->sin6_addr), addr_str, INET6_ADDRSTRLEN) != 0){ destIp = addr_str; - port = ntohs(((struct sockaddr_in6 *)recvAddr)->sin6_port); + port = ntohs(((struct sockaddr_in6 *)(char*)recvAddr)->sin6_port); } } - if (((struct sockaddr_in *)recvAddr)->sin_family == AF_INET){ - if (inet_ntop(AF_INET, &(((struct sockaddr_in *)recvAddr)->sin_addr), addr_str, INET6_ADDRSTRLEN) != 0){ + if (((struct sockaddr *)(char*)recvAddr)->sa_family == AF_INET){ + if (inet_ntop(AF_INET, &(((struct sockaddr_in *)(char*)recvAddr)->sin_addr), addr_str, INET6_ADDRSTRLEN) != 0){ destIp = addr_str; - port = ntohs(((struct sockaddr_in *)recvAddr)->sin_port); + port = ntohs(((struct sockaddr_in *)(char*)recvAddr)->sin_port); } } - int ret = ::bind(sock, (const struct sockaddr*)recvAddr, recvAddr_size); + int ret = ::bind(sock, (const struct sockaddr*)(char*)recvAddr, recvAddr.size()); if (!ret){ INFO_MSG("Bound socket %d to %s:%" PRIu32, sock, destIp.c_str(), port); }else{ - FAIL_MSG("Failed to bind socket %d (%s) %s:%" PRIu32 ": %s", sock, addrFam(((struct sockaddr_in *)recvAddr)->sin_family), destIp.c_str(), port, strerror(errno)); - std::ofstream bleh("/tmp/socket_recv"); - bleh.write((const char*)recvAddr, recvAddr_size); - bleh.write((const char*)destAddr, destAddr_size); - bleh.close(); + FAIL_MSG("Failed to bind socket %d (%s) %s:%" PRIu32 ": %s", sock, addrFam(((struct sockaddr *)(char*)recvAddr)->sa_family), destIp.c_str(), port, strerror(errno)); return false; } } @@ -2608,19 +2704,19 @@ bool Socket::UDPConnection::connect(){ std::string destIp; uint32_t port; char addr_str[INET6_ADDRSTRLEN + 1]; - if (((struct sockaddr_in *)destAddr)->sin_family == AF_INET6){ - if (inet_ntop(AF_INET6, &(((struct sockaddr_in6 *)destAddr)->sin6_addr), addr_str, INET6_ADDRSTRLEN) != 0){ + if (((struct sockaddr *)(char*)destAddr)->sa_family == AF_INET6){ + if (inet_ntop(AF_INET6, &(((struct sockaddr_in6 *)(char*)destAddr)->sin6_addr), addr_str, INET6_ADDRSTRLEN) != 0){ destIp = addr_str; - port = ntohs(((struct sockaddr_in6 *)destAddr)->sin6_port); + port = ntohs(((struct sockaddr_in6 *)(char*)destAddr)->sin6_port); } } - if (((struct sockaddr_in *)destAddr)->sin_family == AF_INET){ - if (inet_ntop(AF_INET, &(((struct sockaddr_in *)destAddr)->sin_addr), addr_str, INET6_ADDRSTRLEN) != 0){ + if (((struct sockaddr *)(char*)destAddr)->sa_family == AF_INET){ + if (inet_ntop(AF_INET, &(((struct sockaddr_in *)(char*)destAddr)->sin_addr), addr_str, INET6_ADDRSTRLEN) != 0){ destIp = addr_str; - port = ntohs(((struct sockaddr_in *)destAddr)->sin_port); + port = ntohs(((struct sockaddr_in *)(char*)destAddr)->sin_port); } } - int ret = ::connect(sock, (const struct sockaddr*)destAddr, destAddr_size); + int ret = ::connect(sock, (const struct sockaddr*)(char*)destAddr, destAddr.size()); if (!ret){ INFO_MSG("Connected socket to %s:%" PRIu32, destIp.c_str(), port); }else{ @@ -2685,18 +2781,37 @@ bool Socket::UDPConnection::Receive(){ if (errno != EAGAIN){INFO_MSG("UDP receive: %d (%s)", errno, strerror(errno));} return false; } - if (destAddr && destsize && destAddr_size >= destsize){memcpy(destAddr, &addr, destsize);} -#if !defined(__CYGWIN__) && !defined(_WIN32) - if (recvAddr){ + if (destAddr.size() && destsize){destAddr.assign(&addr, destsize);} +#ifdef HASPKTINFO + if (recvAddr.size()){ for ( struct cmsghdr *cmsg = CMSG_FIRSTHDR(&mHdr); cmsg != NULL; cmsg = CMSG_NXTHDR(&mHdr, cmsg)){ - if (cmsg->cmsg_level != IPPROTO_IP || cmsg->cmsg_type != IP_PKTINFO){continue;} - struct in_pktinfo* pi = (in_pktinfo*)CMSG_DATA(cmsg); - struct sockaddr_in * recvCast = (sockaddr_in*)recvAddr; - recvCast->sin_family = family; - recvCast->sin_port = htons(boundPort); - memcpy(&(recvCast->sin_addr), &(pi->ipi_spec_dst), sizeof(pi->ipi_spec_dst)); - recvInterface = pi->ipi_ifindex; - hasReceiveData = true; + if (cmsg->cmsg_level == IPPROTO_IP && cmsg->cmsg_type == IP_PKTINFO){ + struct in_pktinfo* pi = (in_pktinfo*)CMSG_DATA(cmsg); + if (family == AF_INET6){ + struct sockaddr_in6 * recvCast = (sockaddr_in6*)(char*)recvAddr; + recvCast->sin6_port = htons(boundPort); + recvCast->sin6_family = AF_INET6; + memcpy(((char*)&(recvCast->sin6_addr)) + 12, &(pi->ipi_spec_dst), sizeof(pi->ipi_spec_dst)); + memset((void*)&(recvCast->sin6_addr), 0, 10); + memset((char*)&(recvCast->sin6_addr) + 10, 255, 2); + }else{ + struct sockaddr_in * recvCast = (sockaddr_in*)(char*)recvAddr; + recvCast->sin_port = htons(boundPort); + recvCast->sin_family = AF_INET; + memcpy(&(recvCast->sin_addr), &(pi->ipi_spec_dst), sizeof(pi->ipi_spec_dst)); + } + recvInterface = pi->ipi_ifindex; + hasReceiveData = true; + } + if (cmsg->cmsg_level == IPPROTO_IPV6 && cmsg->cmsg_type == IPV6_PKTINFO){ + struct in6_pktinfo* pi = (in6_pktinfo*)CMSG_DATA(cmsg); + struct sockaddr_in6 * recvCast = (sockaddr_in6*)(char*)recvAddr; + recvCast->sin6_family = AF_INET6; + recvCast->sin6_port = htons(boundPort); + memcpy(&(recvCast->sin6_addr), &(pi->ipi6_addr), sizeof(pi->ipi6_addr)); + recvInterface = pi->ipi6_ifindex; + hasReceiveData = true; + } } } #endif diff --git a/lib/socket.h b/lib/socket.h index 97785765..d0ca67e6 100644 --- a/lib/socket.h +++ b/lib/socket.h @@ -51,10 +51,13 @@ namespace Buffer{ /// Holds Socket tools. namespace Socket{ + std::string sockaddrToString(const sockaddr* A); void hostBytesToStr(const char *bytes, size_t len, std::string &target); bool isBinAddress(const std::string &binAddr, std::string matchTo); bool matchIPv6Addr(const std::string &A, const std::string &B, uint8_t prefix); + bool compareAddress(const sockaddr* A, const sockaddr* B); std::string getBinForms(std::string addr); + std::deque getAddrs(std::string addr, uint16_t port, int family = AF_UNSPEC); /// Returns true if given human-readable address (address, not hostname) is a local address. bool isLocal(const std::string &host); /// Returns true if given human-readable hostname is a local address. @@ -215,14 +218,12 @@ namespace Socket{ class UDPConnection{ private: void init(bool nonblock, int family = AF_INET6); - int sock; ///< Internally saved socket number. + int sock; ///< Internally saved socket number std::string remotehost; ///< Stores remote host address - void *destAddr; ///< Destination address pointer. - unsigned int destAddr_size; ///< Size of the destination address pointer. - void *recvAddr; ///< Destination address pointer. - unsigned int recvAddr_size; ///< Size of the destination address pointer. - unsigned int up; ///< Amount of bytes transferred up. - unsigned int down; ///< Amount of bytes transferred down. + Util::ResizeablePointer destAddr; ///< Destination address + Util::ResizeablePointer recvAddr; ///< Local address + unsigned int up; ///< Amount of bytes transferred up + unsigned int down; ///< Amount of bytes transferred down int family; ///< Current socket address family std::string boundAddr, boundMulti; int boundPort; @@ -233,6 +234,7 @@ namespace Socket{ bool hasReceiveData; bool isBlocking; bool isConnected; + bool ignoreSendErrors; bool pretendReceive; ///< If true, will pretend to have just received the current data buffer on new Receive() call bool onData(); @@ -254,6 +256,7 @@ namespace Socket{ UDPConnection(const UDPConnection &o); UDPConnection(bool nonblock = false); ~UDPConnection(); + void assimilate(int sock); bool operator==(const UDPConnection& b) const; operator bool() const; #ifdef SSL @@ -269,14 +272,18 @@ namespace Socket{ uint16_t bind(int port, std::string iface = "", const std::string &multicastAddress = ""); bool connect(); void setBlocking(bool blocking); + void setIgnoreSendErrors(bool ign); void allocateDestination(); void SetDestination(std::string hostname, uint32_t port); + bool setDestination(sockaddr * addr, size_t size); + const Util::ResizeablePointer & getRemoteAddr() const; void GetDestination(std::string &hostname, uint32_t &port); void GetLocalDestination(std::string &hostname, uint32_t &port); std::string getBinDestination(); const void * getDestAddr(){return destAddr;} - size_t getDestAddrLen(){return destAddr_size;} + size_t getDestAddrLen(){return destAddr.size();} std::string getBoundAddress(); + uint16_t getBoundPort() const; uint32_t getDestPort() const; bool Receive(); void SendNow(const std::string &data); diff --git a/lib/socket_srt.cpp b/lib/socket_srt.cpp index 091e34e1..21e60748 100644 --- a/lib/socket_srt.cpp +++ b/lib/socket_srt.cpp @@ -1,5 +1,5 @@ #include "defines.h" -#include "lib/http_parser.h" +#include "http_parser.h" #include "socket_srt.h" #include "json.h" #include "timing.h" @@ -9,6 +9,15 @@ #define INVALID_SRT_SOCKET -1 +/// Calls gai_strerror with the given argument, calling regular strerror on the global errno as needed +static const char *gai_strmagic(int errcode){ + if (errcode == EAI_SYSTEM){ + return strerror(errno); + }else{ + return gai_strerror(errcode); + } +} + namespace Socket{ namespace SRT{ bool isInited = false; @@ -26,6 +35,7 @@ namespace Socket{ bool libraryCleanup(){ if (isInited){ + alarm(2); srt_cleanup(); isInited = false; } @@ -41,17 +51,36 @@ namespace Socket{ sockaddr_in createInetAddr(const std::string &_host, int _port){ sockaddr_in res; - memset(&res, 9, sizeof res); - res.sin_family = AF_INET; - res.sin_port = htons(_port); + memset(&res, 0, sizeof res); + struct addrinfo *result, *rp, hints; + std::stringstream ss; + ss << _port; - if (_host != ""){ - if (inet_pton(AF_INET, _host.c_str(), &res.sin_addr) == 1){return res;} - hostent *he = gethostbyname(_host.c_str()); - if (!he || he->h_addrtype != AF_INET){ERROR_MSG("Host not found %s", _host.c_str());} - res.sin_addr = *(in_addr *)he->h_addr_list[0]; + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_family = AF_INET6; + hints.ai_socktype = SOCK_DGRAM; + hints.ai_flags = AI_ADDRCONFIG | AI_ALL; + hints.ai_protocol = IPPROTO_UDP; + hints.ai_canonname = NULL; + hints.ai_addr = NULL; + hints.ai_next = NULL; + int s = getaddrinfo(_host.c_str(), ss.str().c_str(), &hints, &result); + if (s != 0){ + hints.ai_family = AF_UNSPEC; + s = getaddrinfo(_host.c_str(), ss.str().c_str(), &hints, &result); + if (s != 0){ + FAIL_MSG("Could not connect SRT socket to %s:%i! Error: %s", _host.c_str(), _port, gai_strmagic(s)); + return res; + } } + for (rp = result; rp != NULL; rp = rp->ai_next){ + size_t maxSize = rp->ai_addrlen; + if (maxSize > sizeof(res)){maxSize = sizeof(res);} + memcpy(&res, rp->ai_addr, maxSize); + break; + } + freeaddrinfo(result); return res; } @@ -91,9 +120,9 @@ namespace Socket{ direction = rhs.direction; remotehost = rhs.remotehost; sock = rhs.sock; + HIGH_MSG("COPIED SRT socket %d", sock); performanceMonitor = rhs.performanceMonitor; host = rhs.host; - outgoing_port = rhs.outgoing_port; prev_pktseq = rhs.prev_pktseq; lastGood = rhs.lastGood; chunkTransmitSize = rhs.chunkTransmitSize; @@ -110,17 +139,94 @@ namespace Socket{ SRTConnection::SRTConnection(const std::string &_host, int _port, const std::string &_direction, const std::map &_params){ + initializeEmpty(); connect(_host, _port, _direction, _params); } + SRTConnection::SRTConnection(Socket::UDPConnection & _udpsocket, const std::string &_direction, const paramList &_params){ + initializeEmpty(); + direction = "output"; + handleConnectionParameters("", _params); + HIGH_MSG("Opening SRT connection in %s mode (%s) on socket %d", modeName.c_str(), direction.c_str(), _udpsocket.getSock()); + + // Copy address from UDP socket + memcpy(&remoteaddr, _udpsocket.getDestAddr(), _udpsocket.getDestAddrLen()); + static char addrconv[INET6_ADDRSTRLEN]; + if (remoteaddr.sin6_family == AF_INET6){ + remotehost = inet_ntop(AF_INET6, &(remoteaddr.sin6_addr), addrconv, INET6_ADDRSTRLEN); + HIGH_MSG("IPv6 addr [%s]", remotehost.c_str()); + } + if (remoteaddr.sin6_family == AF_INET){ + remotehost = inet_ntop(AF_INET, &(((sockaddr_in *)&remoteaddr)->sin_addr), addrconv, INET6_ADDRSTRLEN); + HIGH_MSG("IPv4 addr [%s]", remotehost.c_str()); + } + + sock = srt_create_socket(); + HIGH_MSG("Opened SRT socket %d", sock); + + if (_direction == "rendezvous"){ + bool v = true; + srt_setsockopt(sock, 0, SRTO_RENDEZVOUS, &v, sizeof v); + } + + if (preConfigureSocket() == SRT_ERROR){ + ERROR_MSG("Error configuring SRT socket"); + return; + } + + srt_bind_acquire(sock, _udpsocket.getSock()); + if (sock == SRT_INVALID_SOCK){ + ERROR_MSG("Error creating an SRT socket from bound UDP socket"); + return; + } + + lastGood = Util::bootMS(); + if (_direction == "rendezvous"){return;} + + srt_listen(sock, 1); + SRTSOCKET tmpSock = srt_accept_bond(&sock, 1, 10000); + HIGH_MSG("Opened SRT socket %d", tmpSock); + close(); + sock = tmpSock; + + if (sock == SRT_INVALID_SOCK){ + FAIL_MSG("SRT error: %s", srt_getlasterror_str()); + return; + } + + if (postConfigureSocket() == SRT_ERROR){ + ERROR_MSG("Error during postconfigure socket"); + return; + } + HIGH_MSG("UDP to SRT socket conversion %" PRId32 ": %s", sock, getStateStr()); + } + + const char * SRTConnection::getStateStr(){ + if (sock == INVALID_SRT_SOCKET){return "invalid / closed";} + int state = srt_getsockstate(sock); + switch (state){ + case SRTS_INIT: return "init"; + case SRTS_OPENED: return "opened"; + case SRTS_LISTENING: return "listening"; + case SRTS_CONNECTING: return "connecting"; + case SRTS_CONNECTED: return "connected"; + case SRTS_BROKEN: return "broken"; + case SRTS_CLOSING: return "closing"; + case SRTS_CLOSED: return "closed"; + case SRTS_NONEXIST: return "does not exist"; + } + return "unknown"; + } + SRTConnection::SRTConnection(SRTSOCKET alreadyConnected){ initializeEmpty(); sock = alreadyConnected; + HIGH_MSG("COPIED SRT socket %d", sock); } std::string SRTConnection::getStreamName(){ int sNameLen = 512; - char sName[sNameLen]; + char sName[512]; int optRes = srt_getsockflag(sock, SRTO_STREAMID, (void *)sName, &sNameLen); if (optRes != -1 && sNameLen){return sName;} return ""; @@ -158,8 +264,8 @@ namespace Socket{ } if (err == SRT_ENOCONN){ if (Util::bootMS() > lastGood + 5000){ - ERROR_MSG("SRT connection timed out - closing"); - close(); + ERROR_MSG("SRT connection timed out"); + timedOut = true; } return 0; } @@ -169,6 +275,7 @@ namespace Socket{ } if (receivedBytes == 0){ close(); + return 0; }else{ lastGood = Util::bootMS(); } @@ -188,13 +295,14 @@ namespace Socket{ int err = srt_getlasterror(0); if (err == SRT_EASYNCRCV){return 0;} if (err == SRT_ECONNLOST){ + INFO_MSG("SRT connection %d lost", sock); close(); return 0; } if (err == SRT_ENOCONN){ if (Util::bootMS() > lastGood + 5000){ ERROR_MSG("SRT connection timed out - closing"); - close(); + timedOut = true; } return 0; } @@ -203,7 +311,9 @@ namespace Socket{ return 0; } if (receivedBytes == 0){ + INFO_MSG("SRT connection %d closed", sock); close(); + return 0; }else{ lastGood = Util::bootMS(); } @@ -213,51 +323,43 @@ namespace Socket{ void SRTConnection::connect(const std::string &_host, int _port, const std::string &_direction, const std::map &_params){ - initializeEmpty(); - direction = _direction; - + timedOut = false; handleConnectionParameters(_host, _params); - HIGH_MSG("Opening SRT connection %s in %s mode on %s:%d", modeName.c_str(), direction.c_str(), _host.c_str(), _port); - sock = srt_create_socket(); - if (sock == SRT_ERROR){ - ERROR_MSG("Error creating an SRT socket"); - return; - } - if (modeName == "rendezvous"){ - bool v = true; - srt_setsockopt(sock, 0, SRTO_RENDEZVOUS, &v, sizeof v); - } - if (preConfigureSocket() == SRT_ERROR){ - ERROR_MSG("Error configuring SRT socket"); - return; + if (sock == SRT_INVALID_SOCK){ + sock = srt_create_socket(); + HIGH_MSG("Opened SRT socket %d", sock); + if (sock == SRT_INVALID_SOCK){ + ERROR_MSG("Error creating an SRT socket"); + return; + } + if (preConfigureSocket() == SRT_ERROR){ + ERROR_MSG("Error configuring SRT socket"); + return; + } } if (modeName == "caller"){ - if (outgoing_port){setupAdapter("", outgoing_port);} + std::deque addrs = Socket::getAddrs(_host, _port); + for (std::deque::iterator it = addrs.begin(); it != addrs.end(); ++it){ + size_t maxSize = it->size(); + if (maxSize > sizeof(remoteaddr)){maxSize = sizeof(remoteaddr);} + memcpy(&remoteaddr, it->data(), maxSize); - sockaddr_in sa = createInetAddr(_host, _port); - memcpy(&remoteaddr, &sa, sizeof(sockaddr_in)); - sockaddr *psa = (sockaddr *)&sa; - - HIGH_MSG("Going to connect sock %d", sock); - if (srt_connect(sock, psa, sizeof sa) == SRT_ERROR){ - srt_close(sock); - sock = -1; - ERROR_MSG("Can't connect SRT Socket"); - return; + sockaddr *psa = (sockaddr *)&remoteaddr; + HIGH_MSG("Going to connect sock %d", sock); + if (srt_connect(sock, psa, sizeof remoteaddr) != SRT_ERROR){ + if (postConfigureSocket() == SRT_ERROR){ERROR_MSG("Error during postconfigure socket");} + INFO_MSG("Caller SRT socket %" PRId32 " success targetting %s:%u", sock, _host.c_str(), _port); + lastGood = Util::bootMS(); + return; + } } - HIGH_MSG("Connected sock %d", sock); - - if (postConfigureSocket() == SRT_ERROR){ - ERROR_MSG("Error during postconfigure socket"); - return; - } - INFO_MSG("Caller SRT socket %" PRId32 " success targetting %s:%u", sock, _host.c_str(), _port); - lastGood = Util::bootMS(); + close(); + ERROR_MSG("Can't connect SRT socket to any address for %s", _host.c_str()); return; } if (modeName == "listener"){ @@ -267,61 +369,19 @@ namespace Socket{ sockaddr *psa = (sockaddr *)&sa; if (srt_bind(sock, psa, sizeof sa) == SRT_ERROR){ - srt_close(sock); - sock = -1; + close(); ERROR_MSG("Can't connect SRT Socket: %s", srt_getlasterror_str()); return; } if (srt_listen(sock, 100) == SRT_ERROR){ - srt_close(sock); - sock = -1; + close(); ERROR_MSG("Can not listen on Socket"); } INFO_MSG("Listener SRT socket success @ %s:%u", _host.c_str(), _port); lastGood = Util::bootMS(); return; } - if (modeName == "rendezvous"){ - int outport = (outgoing_port ? outgoing_port : _port); - HIGH_MSG("Going to bind a server on %s:%u", _host.c_str(), _port); - - sockaddr_in sa = createInetAddr(_host, outport); - sockaddr *psa = (sockaddr *)&sa; - - if (srt_bind(sock, psa, sizeof sa) == SRT_ERROR){ - srt_close(sock); - sock = -1; - ERROR_MSG("Can't connect SRT Socket"); - return; - } - - sockaddr_in sb = createInetAddr(_host, outport); - sockaddr *psb = (sockaddr *)&sb; - - if (srt_connect(sock, psb, sizeof sb) == SRT_ERROR){ - srt_close(sock); - sock = -1; - ERROR_MSG("Can't connect SRT Socket"); - return; - } - - if (postConfigureSocket() == SRT_ERROR){ - ERROR_MSG("Error during postconfigure socket"); - return; - } - INFO_MSG("Rendezvous SRT socket success @ %s:%u", _host.c_str(), _port); - lastGood = Util::bootMS(); - return; - } - ERROR_MSG("Invalid mode parameter. Use 'client' or 'server'"); - } - - void SRTConnection::setupAdapter(const std::string &_host, int _port){ - sockaddr_in localsa = createInetAddr(_host, _port); - sockaddr *psa = (sockaddr *)&localsa; - if (srt_bind(sock, psa, sizeof localsa) == SRT_ERROR){ - ERROR_MSG("Unable to bind socket to %s:%u", _host.c_str(), _port); - } + ERROR_MSG("Invalid mode parameter. Use 'caller' or 'listener'"); } void SRTConnection::SendNow(const std::string &data){SendNow(data.data(), data.size());} @@ -338,14 +398,17 @@ namespace Socket{ return; } if (err == SRT_ENOCONN){ - if (Util::bootMS() > lastGood + 5000){ + if (Util::bootMS() > lastGood + 10000){ ERROR_MSG("SRT connection timed out - closing"); - close(); + timedOut = true; } return; } // ERROR_MSG("Unable to send data over socket %" PRId32 ": %s", sock, srt_getlasterror_str()); - if (srt_getsockstate(sock) != SRTS_CONNECTED){close();} + if (srt_getsockstate(sock) != SRTS_CONNECTED){ + close(); + return; + } }else{ lastGood = Util::bootMS(); } @@ -378,9 +441,9 @@ namespace Socket{ memset(&performanceMonitor, 0, sizeof(performanceMonitor)); prev_pktseq = 0; sock = SRT_INVALID_SOCK; - outgoing_port = 0; chunkTransmitSize = 1316; blocking = false; + timedOut = false; timeout = 0; } @@ -397,9 +460,9 @@ namespace Socket{ void SRTConnection::handleConnectionParameters(const std::string &_host, const std::map &_params){ params = _params; - DONTEVEN_MSG("SRT Received parameters: "); + VERYHIGH_MSG("SRT Received parameters: "); for (std::map::const_iterator it = params.begin(); it != params.end(); it++){ - DONTEVEN_MSG(" %s: %s", it->first.c_str(), it->second.c_str()); + VERYHIGH_MSG(" %s: %s", it->first.c_str(), it->second.c_str()); } adapter = (params.count("adapter") ? params.at("adapter") : ""); @@ -417,8 +480,6 @@ namespace Socket{ tsbpdMode = (params.count("tsbpd") && JSON::Value(params.at("tsbpd")).asBool()); - outgoing_port = (params.count("port") ? strtol(params.at("port").c_str(), 0, 0) : 0); - if ((!params.count("transtype") || params.at("transtype") != "file") && chunkTransmitSize > SRT_LIVE_DEF_PLSIZE){ if (chunkTransmitSize > SRT_LIVE_MAX_PLSIZE){ ERROR_MSG("Chunk size in live mode exceeds 1456 bytes!"); @@ -439,12 +500,10 @@ namespace Socket{ } if (srt_setsockopt(sock, 0, SRTO_RCVSYN, &no, sizeof no) == -1){return -1;} - if (params.count("linger")){ - linger lin; - lin.l_linger = atoi(params.at("linger").c_str()); - lin.l_onoff = lin.l_linger > 0 ? 1 : 0; - srt_setsockopt(sock, 0, SRTO_LINGER, &lin, sizeof(linger)); - } + linger lin; + lin.l_linger = params.count("linger") ? atoi(params.at("linger").c_str()) : 0; + lin.l_onoff = lin.l_linger ? 1 : 0; + srt_setsockopt(sock, 0, SRTO_LINGER, &lin, sizeof(linger)); std::string errMsg = configureSocketLoop(SRT::SockOpt::PRE); if (errMsg.size()){ @@ -490,9 +549,11 @@ namespace Socket{ } void SRTConnection::close(){ - if (sock != -1){ + if (sock != INVALID_SRT_SOCKET){ + HIGH_MSG("Closing SRT socket %d", sock); + setBlocking(true); srt_close(sock); - sock = -1; + sock = INVALID_SRT_SOCKET; } } diff --git a/lib/socket_srt.h b/lib/socket_srt.h index c25ec1da..e1e97491 100644 --- a/lib/socket_srt.h +++ b/lib/socket_srt.h @@ -37,12 +37,14 @@ namespace Socket{ SRTConnection(SRTSOCKET alreadyConnected); SRTConnection(const std::string &_host, int _port, const std::string &_direction = "input", const paramList &_params = paramList()); + SRTConnection(Socket::UDPConnection & _udpsocket, const std::string &_direction, const paramList &_params); void connect(const std::string &_host, int _port, const std::string &_direction = "input", const paramList &_params = paramList()); void close(); - bool connected() const{return sock != -1;} + bool connected() const{return (sock != -1) && !timedOut;} operator bool() const{return connected();} + const char * getStateStr(); void setBlocking(bool blocking); ///< Set this socket to be blocking (true) or nonblocking (false). bool isBlocking(); ///< Check if this socket is blocking (true) or nonblocking (false). @@ -77,9 +79,9 @@ namespace Socket{ CBytePerfMon performanceMonitor; std::string host; - int outgoing_port; int32_t prev_pktseq; uint64_t lastGood; + bool timedOut; uint32_t chunkTransmitSize; @@ -94,7 +96,6 @@ namespace Socket{ void handleConnectionParameters(const std::string &_host, const paramList &_params); int preConfigureSocket(); std::string configureSocketLoop(SRT::SockOpt::Binding _binding); - void setupAdapter(const std::string &_host, int _port); bool blocking; }; diff --git a/lib/util.cpp b/lib/util.cpp index e488e4aa..b434194d 100644 --- a/lib/util.cpp +++ b/lib/util.cpp @@ -361,6 +361,20 @@ namespace Util{ maxSize = 0; } + ResizeablePointer::ResizeablePointer(const ResizeablePointer & rhs){ + currSize = 0; + ptr = 0; + maxSize = 0; + append(rhs, rhs.size()); + } + + ResizeablePointer& ResizeablePointer::operator= (const ResizeablePointer& rhs){ + if (this == &rhs){return *this;} + truncate(0); + append(rhs, rhs.size()); + return *this; + } + void ResizeablePointer::shift(size_t byteCount){ //Shifting the entire buffer is easy, we do nothing and set size to zero if (byteCount >= currSize){ @@ -372,6 +386,20 @@ namespace Util{ currSize -= byteCount; } + /// Takes another ResizeablePointer as argument and swaps their pointers around, + /// thus exchanging them without needing to copy anything. + void ResizeablePointer::swap(ResizeablePointer & rhs){ + void * tmpPtr = ptr; + size_t tmpCurrSize = currSize; + size_t tmpMaxSize = maxSize; + ptr = rhs.ptr; + currSize = rhs.currSize; + maxSize = rhs.maxSize; + rhs.ptr = tmpPtr; + rhs.currSize = tmpCurrSize; + rhs.maxSize = tmpMaxSize; + } + bool ResizeablePointer::assign(const void *p, uint32_t l){ if (!allocate(l)){return false;} memcpy(ptr, p, l); @@ -406,12 +434,7 @@ namespace Util{ bool ResizeablePointer::allocate(uint32_t l){ if (l > maxSize){ - void *tmp = 0; - if (!ptr){ - tmp = malloc(l); - }else{ - tmp = realloc(ptr, l); - } + void *tmp = realloc(ptr, l); if (!tmp){ FAIL_MSG("Could not allocate %" PRIu32 " bytes of memory", l); return false; diff --git a/lib/util.h b/lib/util.h index c656edf2..b9d3a178 100644 --- a/lib/util.h +++ b/lib/util.h @@ -48,6 +48,8 @@ namespace Util{ class ResizeablePointer{ public: ResizeablePointer(); + ResizeablePointer(const ResizeablePointer & rhs); + ResizeablePointer& operator= (const ResizeablePointer& rhs); ~ResizeablePointer(); inline size_t &size(){return currSize;} inline const size_t size() const{return currSize;} @@ -57,6 +59,7 @@ namespace Util{ bool append(const std::string &str); bool allocate(uint32_t l); void shift(size_t byteCount); + void swap(ResizeablePointer & rhs); uint32_t rsize(); void truncate(const size_t newLen); inline operator char *(){return (char *)ptr;} diff --git a/meson.build b/meson.build index af8314fb..d3b8c590 100644 --- a/meson.build +++ b/meson.build @@ -100,6 +100,9 @@ message('Building release @0@ for version @1@ @ debug level @2@'.format(release, mist_deps = [] ccpp = meson.get_compiler('cpp') +if ccpp.has_header_symbol('netinet/in.h', 'IPV6_RECVPKTINFO') and ccpp.has_header_symbol('netinet/in.h', 'IP_PKTINFO') + option_defines += '-DHASPKTINFO' +endif if usessl mbedtls = ccpp.find_library('mbedtls', required: false) diff --git a/src/controller/controller_statistics.cpp b/src/controller/controller_statistics.cpp index f64d829d..454c1fe5 100644 --- a/src/controller/controller_statistics.cpp +++ b/src/controller/controller_statistics.cpp @@ -174,13 +174,23 @@ void Controller::updateBandwidthConfig(){ /// This function is ran whenever a stream becomes active. void Controller::streamStarted(std::string stream){ INFO_MSG("Stream %s became active", stream.c_str()); - if (tagQueue.count(stream)){ - tagQueueItem & q = tagQueue[stream]; - for (std::set::iterator it = q.tags.begin(); it != q.tags.end(); ++it){ - streamStats[stream].tags.insert(*it); + { + streamTotals & sT = streamStats[stream]; + JSON::Value strCnf = Util::getStreamConfig(stream); + if (strCnf.isMember("tags")){ + jsonForEachConst(strCnf["tags"], it){ + sT.tags.insert(it->asString()); + } + INFO_MSG("Applied %" PRIu32 " tags to stream %s from config",strCnf["tags"].size() , stream.c_str()); + } + if (tagQueue.count(stream)){ + tagQueueItem & q = tagQueue[stream]; + for (std::set::iterator it = q.tags.begin(); it != q.tags.end(); ++it){ + sT.tags.insert(*it); + } + INFO_MSG("Applied %zu tags to stream %s retroactively",q.tags.size() , stream.c_str()); + tagQueue.erase(stream); } - INFO_MSG("Applied %zu tags to stream %s retroactively",q.tags.size() , stream.c_str()); - tagQueue.erase(stream); } Controller::doAutoPush(stream); } @@ -425,7 +435,7 @@ void Controller::SharedMemStats(void *config){ for (uint64_t cPos = startPos; cPos < endPos; ++cPos){ std::string strm = strmStats->getPointer("stream", cPos); std::string tags = strmStats->getPointer("tags", cPos); - if (tags.size() && streamStats.count(strm)){ + if (strm.size() && tags.size() && streamStats.count(strm)){ INFO_MSG("Restoring stream tags: %s -> %s", strm.c_str(), tags.c_str()); streamTotals & st = streamStats[strm]; while (tags.size()){ @@ -468,7 +478,8 @@ void Controller::SharedMemStats(void *config){ cutOffPoint = 0; } for (std::map::iterator it = sessions.begin(); it != sessions.end(); it++){ - streamStats[it->second.getStreamName()].currSessions++; + std::string strm = it->second.getStreamName(); + if (strm.size()){streamStats[strm].currSessions++;} // This part handles ending sessions, keeping them in cache for now if (it->second.getEnd() < cutOffPoint){ viewSecondsTotal += it->second.getConnTime(); @@ -476,30 +487,32 @@ void Controller::SharedMemStats(void *config){ // Don't count this session as a viewer continue; } - // Recount input, output and viewer type sessions - switch (it->second.getSessType()){ - case SESS_UNSET: break; - case SESS_VIEWER: - if (it->second.hasDataFor(tOut)){ - streamStats[it->second.getStreamName()].currViews++; + if (strm.size()){ + // Recount input, output and viewer type sessions + switch (it->second.getSessType()){ + case SESS_UNSET: break; + case SESS_VIEWER: + if (it->second.hasDataFor(tOut)){ + streamStats[it->second.getStreamName()].currViews++; + } + servSeconds += it->second.getConnTime(); + break; + case SESS_INPUT: + if (it->second.hasDataFor(tIn)){ + streamStats[it->second.getStreamName()].currIns++; + } + break; + case SESS_OUTPUT: + if (it->second.hasDataFor(tOut)){ + streamStats[it->second.getStreamName()].currOuts++; + } + break; + case SESS_UNSPECIFIED: + if (it->second.hasDataFor(tOut)){ + streamStats[it->second.getStreamName()].currUnspecified++; + } + break; } - servSeconds += it->second.getConnTime(); - break; - case SESS_INPUT: - if (it->second.hasDataFor(tIn)){ - streamStats[it->second.getStreamName()].currIns++; - } - break; - case SESS_OUTPUT: - if (it->second.hasDataFor(tOut)){ - streamStats[it->second.getStreamName()].currOuts++; - } - break; - case SESS_UNSPECIFIED: - if (it->second.hasDataFor(tOut)){ - streamStats[it->second.getStreamName()].currUnspecified++; - } - break; } } while (mustWipe.size()){ @@ -753,14 +766,15 @@ void Controller::statSession::update(uint64_t index, Comms::Sessions &statComm){ } } // Only count connections that are countable - if (noBWCount != 2){ + if (noBWCount != 2 && streamName.size()){ createEmptyStatsIfNeeded(streamName); - streamStats[streamName].upBytes += currUp - prevUp; - streamStats[streamName].downBytes += currDown - prevDown; - streamStats[streamName].packSent += currPktSent - prevPktSent; - streamStats[streamName].packLoss += currPktLost - prevPktLost; - streamStats[streamName].packRetrans += currPktRetrans - prevPktRetrans; - if (sessionType == SESS_VIEWER){streamStats[streamName].viewSeconds += secIncr;} + streamTotals & sT = streamStats[streamName]; + sT.upBytes += currUp - prevUp; + sT.downBytes += currDown - prevDown; + sT.packSent += currPktSent - prevPktSent; + sT.packLoss += currPktLost - prevPktLost; + sT.packRetrans += currPktRetrans - prevPktRetrans; + if (sessionType == SESS_VIEWER){sT.viewSeconds += secIncr;} } } diff --git a/src/input/input_tssrt.cpp b/src/input/input_tssrt.cpp index fc1e1644..a9b39662 100644 --- a/src/input/input_tssrt.cpp +++ b/src/input/input_tssrt.cpp @@ -1,8 +1,10 @@ #include "input_tssrt.h" +#include #include #include #include #include +#include #include #include #include @@ -13,11 +15,9 @@ #include #include #include -#include - +#include #include #include -#include Util::Config *cfgPointer = NULL; std::string baseStreamName; @@ -33,7 +33,6 @@ void signal_handler(int signum, siginfo_t *sigInfo, void *ignore){ } } - // We use threads here for multiple input pushes, because of the internals of the SRT Library static void callThreadCallbackSRT(void *socknum){ // use the accepted socket as the second parameter @@ -47,6 +46,8 @@ namespace Mist{ /// \arg cfg Util::Config that contains all current configurations. InputTSSRT::InputTSSRT(Util::Config *cfg, Socket::SRTConnection s) : Input(cfg){ rawIdx = INVALID_TRACK_ID; + udpInit = 0; + srtConn = 0; lastRawPacket = 0; bootMSOffsetCalculated = false; assembler.setLive(); @@ -136,16 +137,17 @@ namespace Mist{ // Setup if we are called form with a thread for push-based input. if (s.connected()){ - srtConn = s; + srtConn = new Socket::SRTConnection(s); streamName = baseStreamName; - std::string streamid = srtConn.getStreamName(); + std::string streamid = srtConn->getStreamName(); int64_t acc = config->getInteger("acceptable"); if (acc == 0){ if (streamid.size()){streamName += "+" + streamid;} }else if(acc == 2){ if (streamName != streamid){ FAIL_MSG("Stream ID '%s' does not match stream name, push blocked", streamid.c_str()); - srtConn.close(); + srtConn->close(); + srtConn = 0; } } Util::setStreamName(streamName); @@ -169,46 +171,49 @@ namespace Mist{ Socket::SRT::libraryInit(); rawMode = config->getBool("raw"); if (rawMode){INFO_MSG("Entering raw mode");} - if (srtConn.getSocket() == -1){ - std::string source = config->getString("input"); - standAlone = false; - HTTP::URL u(source); - INFO_MSG("Parsed url: %s", u.getUrl().c_str()); - if (Socket::interpretSRTMode(u) == "listener"){ - std::map arguments; - HTTP::parseVars(u.args, arguments); - sSock = Socket::SRTServer(u.getPort(), u.host, arguments, false); - struct sigaction new_action; - struct sigaction cur_action; - new_action.sa_sigaction = signal_handler; - sigemptyset(&new_action.sa_mask); - new_action.sa_flags = SA_SIGINFO; - sigaction(SIGINT, &new_action, &cur_action); - if (cur_action.sa_sigaction && cur_action.sa_sigaction != oldSignal){ - if (oldSignal){WARN_MSG("Multiple signal handlers! I can't deal with this.");} - oldSignal = cur_action.sa_sigaction; - } - sigaction(SIGHUP, &new_action, &cur_action); - if (cur_action.sa_sigaction && cur_action.sa_sigaction != oldSignal){ - if (oldSignal){WARN_MSG("Multiple signal handlers! I can't deal with this.");} - oldSignal = cur_action.sa_sigaction; - } - sigaction(SIGTERM, &new_action, &cur_action); - if (cur_action.sa_sigaction && cur_action.sa_sigaction != oldSignal){ - if (oldSignal){WARN_MSG("Multiple signal handlers! I can't deal with this.");} - oldSignal = cur_action.sa_sigaction; - } - }else{ - std::map arguments; - HTTP::parseVars(u.args, arguments); - size_t connectCnt = 0; - do{ - srtConn.connect(u.host, u.getPort(), "input", arguments); - if (!srtConn){Util::sleep(1000);} - ++connectCnt; - }while (!srtConn && connectCnt < 10); - if (!srtConn){WARN_MSG("Connecting to %s timed out", u.getUrl().c_str());} + if (srtConn && *srtConn){return true;} + std::string source = config->getString("input"); + standAlone = false; + HTTP::URL u(source); + INFO_MSG("Parsed url: %s", u.getUrl().c_str()); + if (Socket::interpretSRTMode(u) == "listener"){ + std::map arguments; + HTTP::parseVars(u.args, arguments); + sSock = Socket::SRTServer(u.getPort(), u.host, arguments, false); + struct sigaction new_action; + struct sigaction cur_action; + new_action.sa_sigaction = signal_handler; + sigemptyset(&new_action.sa_mask); + new_action.sa_flags = SA_SIGINFO; + sigaction(SIGINT, &new_action, &cur_action); + if (cur_action.sa_sigaction && cur_action.sa_sigaction != oldSignal){ + if (oldSignal){WARN_MSG("Multiple signal handlers! I can't deal with this.");} + oldSignal = cur_action.sa_sigaction; } + sigaction(SIGHUP, &new_action, &cur_action); + if (cur_action.sa_sigaction && cur_action.sa_sigaction != oldSignal){ + if (oldSignal){WARN_MSG("Multiple signal handlers! I can't deal with this.");} + oldSignal = cur_action.sa_sigaction; + } + sigaction(SIGTERM, &new_action, &cur_action); + if (cur_action.sa_sigaction && cur_action.sa_sigaction != oldSignal){ + if (oldSignal){WARN_MSG("Multiple signal handlers! I can't deal with this.");} + oldSignal = cur_action.sa_sigaction; + } + }else{ + std::map arguments; + HTTP::parseVars(u.args, arguments); + + std::string addData; + if (arguments.count("streamid")){addData = arguments["streamid"];} + size_t connectCnt = 0; + do{ + if (!srtConn){srtConn = new Socket::SRTConnection();} + srtConn->connect(u.host, u.getPort(), "input", arguments); + if (!*srtConn){Util::sleep(1000);} + ++connectCnt; + }while (!*srtConn && connectCnt < 10); + if (!*srtConn){WARN_MSG("Connecting to %s timed out", u.getUrl().c_str());} } return true; } @@ -217,13 +222,13 @@ namespace Mist{ void InputTSSRT::getNext(size_t idx){ thisPacket.null(); bool hasPacket = tsStream.hasPacket(); - while (!hasPacket && srtConn && config->is_active){ + while (!hasPacket && srtConn && *srtConn && config->is_active){ - size_t recvSize = srtConn.RecvNow(); + size_t recvSize = srtConn->RecvNow(); if (recvSize){ if (rawMode){ keepAlive(); - rawBuffer.append(srtConn.recvbuf, recvSize); + rawBuffer.append(srtConn->recvbuf, recvSize); if (rawBuffer.size() >= 1316 && (lastRawPacket == 0 || lastRawPacket != Util::bootMS())){ if (rawIdx == INVALID_TRACK_ID){ rawIdx = meta.addTrack(); @@ -240,8 +245,8 @@ namespace Mist{ } continue; } - if (assembler.assemble(tsStream, srtConn.recvbuf, recvSize, true)){hasPacket = tsStream.hasPacket();} - }else if (srtConn){ + if (assembler.assemble(tsStream, srtConn->recvbuf, recvSize, true)){hasPacket = tsStream.hasPacket();} + }else if (srtConn && *srtConn){ // This should not happen as the SRT socket is read blocking and won't return until there is // data. But if it does, wait before retry Util::sleep(10); @@ -250,7 +255,7 @@ namespace Mist{ if (hasPacket){tsStream.getEarliestPacket(thisPacket);} if (!thisPacket){ - if (srtConn){ + if (srtConn && *srtConn){ INFO_MSG("Could not getNext TS packet!"); Util::logExitReason(ER_FORMAT_SPECIFIC, "internal TS parser error"); }else{ @@ -286,7 +291,7 @@ namespace Mist{ void InputTSSRT::streamMainLoop(){ // If we do not have a srtConn here, we are the main thread and should start accepting pushes. - if (srtConn.getSocket() == -1){ + if (!srtConn || !*srtConn){ cfgPointer = config; baseStreamName = streamName; while (config->is_active && sSock.connected()){ @@ -304,7 +309,7 @@ namespace Mist{ } // If we are here: we have a proper connection (either accepted or pull input) and should start parsing it as such Input::streamMainLoop(); - srtConn.close(); + srtConn->close(); Socket::SRT::libraryCleanup(); } @@ -313,11 +318,11 @@ namespace Mist{ void InputTSSRT::setSingular(bool newSingular){singularFlag = newSingular;} void InputTSSRT::connStats(Comms::Connections &statComm){ - statComm.setUp(srtConn.dataUp()); - statComm.setDown(srtConn.dataDown()); - statComm.setPacketCount(srtConn.packetCount()); - statComm.setPacketLostCount(srtConn.packetLostCount()); - statComm.setPacketRetransmitCount(srtConn.packetRetransmitCount()); + statComm.setUp(srtConn->dataUp()); + statComm.setDown(srtConn->dataDown()); + statComm.setPacketCount(srtConn->packetCount()); + statComm.setPacketLostCount(srtConn->packetLostCount()); + statComm.setPacketRetransmitCount(srtConn->packetRetransmitCount()); } }// namespace Mist diff --git a/src/input/input_tssrt.h b/src/input/input_tssrt.h index 786eb165..2b8922a0 100644 --- a/src/input/input_tssrt.h +++ b/src/input/input_tssrt.h @@ -4,7 +4,6 @@ #include #include #include -#include #include namespace Mist{ @@ -16,7 +15,7 @@ namespace Mist{ void setSingular(bool newSingular); virtual bool needsLock(); virtual std::string getConnectedBinHost(){ - if (srtConn){return srtConn.getBinHost();} + if (srtConn && *srtConn){return srtConn->getBinHost();} return Input::getConnectedBinHost(); } @@ -38,7 +37,8 @@ namespace Mist{ int64_t timeStampOffset; uint64_t lastTimeStamp; - Socket::SRTConnection srtConn; + Socket::SRTConnection * srtConn; + Socket::UDPConnection * udpInit; bool singularFlag; virtual void connStats(Comms::Connections &statComm); diff --git a/src/output/meson.build b/src/output/meson.build index 80a3c8fe..bbb99393 100644 --- a/src/output/meson.build +++ b/src/output/meson.build @@ -37,7 +37,7 @@ if have_librist endif if have_srt - outputs += {'name' : 'TSSRT', 'format' : 'tssrt', 'extra': ['ts', 'debased', 'with_srt']} + outputs += {'name' : 'TSSRT', 'format' : 'tssrt', 'extra': ['ts', 'with_srt']} endif if get_option('WITH_SANITY') diff --git a/src/output/output.cpp b/src/output/output.cpp index b42e7ce1..e14b352a 100644 --- a/src/output/output.cpp +++ b/src/output/output.cpp @@ -93,6 +93,7 @@ namespace Mist{ Output::Output(Socket::Connection &conn) : myConn(conn){ dataWaitTimeout = 2500; + thisBootMs = Util::bootMS(); pushing = false; recursingSync = false; firstTime = Util::bootMS(); diff --git a/src/output/output_ts.cpp b/src/output/output_ts.cpp index 0631aa36..de9bd951 100644 --- a/src/output/output_ts.cpp +++ b/src/output/output_ts.cpp @@ -74,7 +74,7 @@ namespace Mist{ udpSize = 7; if (targetParams.count("tracks")){tracks = targetParams["tracks"];} if (targetParams.count("pkts")){udpSize = atoi(targetParams["pkts"].c_str());} - packetBuffer.reserve(188 * udpSize); + packetBuffer.allocate(188 * udpSize); if (target.path.size()){ if (!pushSock.bind(0, target.path)){ disconnect(); @@ -230,7 +230,7 @@ namespace Mist{ if (wrapRTP){ // Send RTP packet itself if (rand() % 100 >= dropPercentage){ - tsOut.sendTS(&pushSock, packetBuffer.c_str(), packetBuffer.size()); + tsOut.sendTS(&pushSock, packetBuffer, packetBuffer.size()); myConn.addUp(tsOut.getHsize() + tsOut.getPayloadSize()); } else { INFO_MSG("Dropping RTP packet in order to simulate packet loss"); @@ -239,15 +239,14 @@ namespace Mist{ if (sendFEC){ // Send FEC packet if available uint64_t bytesSent = 0; - tsOut.parseFEC(&fecColumnSock, &fecRowSock, bytesSent, packetBuffer.c_str(), packetBuffer.size()); + tsOut.parseFEC(&fecColumnSock, &fecRowSock, bytesSent, packetBuffer, packetBuffer.size()); myConn.addUp(bytesSent); } }else{ - pushSock.SendNow(packetBuffer); + pushSock.SendNow(packetBuffer, packetBuffer.size()); myConn.addUp(packetBuffer.size()); } - packetBuffer.clear(); - packetBuffer.reserve(udpSize * len); + packetBuffer.truncate(0); curFilled = 0; } packetBuffer.append(tsData, len); diff --git a/src/output/output_ts.h b/src/output/output_ts.h index 9edb5c02..a32abb0b 100644 --- a/src/output/output_ts.h +++ b/src/output/output_ts.h @@ -21,7 +21,7 @@ namespace Mist{ bool wrapRTP; bool sendFEC; void onRTP(void *socket, const char *data, size_t nbytes); - std::string packetBuffer; + Util::ResizeablePointer packetBuffer; Socket::UDPConnection pushSock; Socket::UDPConnection fecColumnSock; Socket::UDPConnection fecRowSock; diff --git a/src/output/output_ts_base.cpp b/src/output/output_ts_base.cpp index 7b414229..91b8bfe4 100644 --- a/src/output/output_ts_base.cpp +++ b/src/output/output_ts_base.cpp @@ -8,6 +8,7 @@ namespace Mist{ setBlocking(true); sendRepeatingHeaders = 0; lastHeaderTime = 0; + maxSkipAhead = 0; } void TSOutput::fillPacket(char const *data, size_t dataLen, bool &firstPack, bool video, @@ -70,6 +71,8 @@ namespace Mist{ return; } } + if (liveSeek(true)){return;} + if (!M.trackLoaded(thisIdx)){return;} // Get ready some data to speed up accesses std::string type = M.getType(thisIdx); std::string codec = M.getCodec(thisIdx); diff --git a/src/output/output_tssrt.cpp b/src/output/output_tssrt.cpp index 8f35ff7d..d2abed7f 100644 --- a/src/output/output_tssrt.cpp +++ b/src/output/output_tssrt.cpp @@ -6,11 +6,13 @@ #include #include #include +#include bool allowStreamNameOverride = true; namespace Mist{ - OutTSSRT::OutTSSRT(Socket::Connection &conn, Socket::SRTConnection & _srtSock) : TSOutput(conn), srtConn(_srtSock){ + OutTSSRT::OutTSSRT(Socket::Connection &conn, Socket::SRTConnection * _srtSock) : TSOutput(conn){ + srtConn = _srtSock; // NOTE: conn is useless for SRT, as it uses a different socket type. sendRepeatingHeaders = 500; // PAT/PMT every 500ms (DVB spec) streamName = config->getString("streamname"); @@ -18,9 +20,14 @@ namespace Mist{ pushOut = false; bootMSOffsetCalculated = false; assembler.setLive(); + udpInit = 0; // Push output configuration if (config->getString("target").size()){ + Socket::SRT::libraryInit(); target = HTTP::URL(config->getString("target")); + HTTP::parseVars(target.args, targetParams); + std::string addData; + if (targetParams.count("streamid")){addData = targetParams["streamid"];} if (target.protocol != "srt"){ FAIL_MSG("Target %s must begin with srt://, aborting", target.getUrl().c_str()); onFail("Invalid srt target: doesn't start with srt://", true); @@ -32,28 +39,27 @@ namespace Mist{ return; } pushOut = true; - HTTP::parseVars(target.args, targetParams); size_t connectCnt = 0; do{ - srtConn.connect(target.host, target.getPort(), "output", targetParams); - if (!srtConn){ + if (!srtConn){srtConn = new Socket::SRTConnection();} + if (srtConn){srtConn->connect(target.host, target.getPort(), "output", targetParams);} + if (!*srtConn){ Util::sleep(1000); }else{ - INFO_MSG("Connect success on attempt %zu", connectCnt+1); + INFO_MSG("SRT socket %s on attempt %zu", srtConn->getStateStr(), connectCnt+1); break; } ++connectCnt; - }while (!srtConn && connectCnt < 5); + }while ((!srtConn || !*srtConn) && connectCnt < 5); if (!srtConn){ FAIL_MSG("Failed to connect to '%s'!", config->getString("target").c_str()); } wantRequest = false; parseData = true; - initialize(); }else{ // Pull output configuration, In this case we have an srt connection in the second constructor parameter. // Handle override / append of streamname options - std::string sName = srtConn.getStreamName(); + std::string sName = srtConn->getStreamName(); if (allowStreamNameOverride){ if (sName != ""){ streamName = sName; @@ -61,18 +67,19 @@ namespace Mist{ Util::setStreamName(streamName); } } + myConn.setHost(srtConn->remotehost); int64_t accTypes = config->getInteger("acceptable"); if (accTypes == 0){//Allow both directions - srtConn.setBlocking(false); + srtConn->setBlocking(false); //Try to read the socket 10 times. If any reads succeed, assume they are pushing in size_t retries = 60; - while (!accTypes && srtConn && retries){ - size_t recvSize = srtConn.Recv(); + while (!accTypes && *srtConn && retries){ + size_t recvSize = srtConn->Recv(); if (recvSize){ accTypes = 2; INFO_MSG("Connection put into ingest mode"); - assembler.assemble(tsIn, srtConn.recvbuf, recvSize, true); + assembler.assemble(tsIn, srtConn->recvbuf, recvSize, true); }else{ Util::sleep(50); } @@ -85,14 +92,14 @@ namespace Mist{ } } if (accTypes == 1){// Only allow outgoing - srtConn.setBlocking(true); - srtConn.direction = "output"; + srtConn->setBlocking(true); + srtConn->direction = "output"; parseData = true; wantRequest = false; initialize(); }else if (accTypes == 2){//Only allow incoming - srtConn.setBlocking(false); - srtConn.direction = "input"; + srtConn->setBlocking(false); + srtConn->direction = "input"; if (Triggers::shouldTrigger("PUSH_REWRITE")){ HTTP::URL reqUrl; reqUrl.protocol = "srt"; @@ -115,7 +122,11 @@ namespace Mist{ Util::sanitizeName(streamName); } } - myConn.setHost(srtConn.remotehost); + if (!streamName.size()){ + Util::logExitReason(ER_FORMAT_SPECIFIC, "Push from %s rejected - there is no stream name set", getConnectedHost().c_str()); + onFinish(); + return; + } if (!allowPush("")){ onFinish(); return; @@ -128,17 +139,48 @@ namespace Mist{ } } + lastWorked = Util::bootSecs(); lastTimeStamp = 0; timeStampOffset = 0; } bool OutTSSRT::onFinish(){ - myConn.close(); - srtConn.close(); + config->is_active = false; return false; } - OutTSSRT::~OutTSSRT(){} + OutTSSRT::~OutTSSRT(){ + if(srtConn){ + srtConn->close(); + delete srtConn; + srtConn = 0; + } + Socket::SRT::libraryCleanup(); + } + + // Override initialSeek to go to last possible position for live streams + void OutTSSRT::initialSeek(bool dryRun){ + if (!meta){return;} + meta.removeLimiter(); + + uint64_t seekPos = 0; + + std::set validTracks = M.getValidTracks(); + if (M.getLive() && validTracks.size()){ + if (userSelect.size()){ + for (std::map::iterator it = userSelect.begin(); it != userSelect.end(); it++){ + if (M.trackValid(it->first) && (M.getNowms(it->first) < seekPos || !seekPos)){ + seekPos = meta.getNowms(it->first); + } + } + }else{ + for (std::set::iterator it = validTracks.begin(); it != validTracks.end(); it++){ + if (meta.getNowms(*it) < seekPos || !seekPos){seekPos = meta.getNowms(*it);} + } + } + } + seek(seekPos); + } static void addIntOpt(JSON::Value & pp, const std::string & param, const std::string & name, const std::string & help, size_t def = 0){ pp[param]["name"] = name; @@ -190,14 +232,6 @@ namespace Mist{ capa["optional"]["streamname"]["short"] = "s"; capa["optional"]["streamname"]["default"] = ""; - capa["optional"]["filelimit"]["name"] = "Open file descriptor limit"; - capa["optional"]["filelimit"]["help"] = "Increase open file descriptor to this value if current system value is lower. A higher value may be needed for handling many concurrent SRT connections."; - - capa["optional"]["filelimit"]["type"] = "int"; - capa["optional"]["filelimit"]["option"] = "--filelimit"; - capa["optional"]["filelimit"]["short"] = "l"; - capa["optional"]["filelimit"]["default"] = "1024"; - capa["optional"]["acceptable"]["name"] = "Acceptable connection types"; capa["optional"]["acceptable"]["help"] = "Whether to allow only incoming pushes (2), only outgoing pulls (1), or both (0, default)"; @@ -313,32 +347,81 @@ namespace Mist{ opt["default"] = ""; opt["help"] = "Which parser to use for data tracks"; config->addOption("datatrack", opt); + + capa["optional"]["passphrase"]["name"] = "Passphrase"; + capa["optional"]["passphrase"]["help"] = "If set, requires a SRT passphrase to connect"; + capa["optional"]["passphrase"]["type"] = "string"; + capa["optional"]["passphrase"]["option"] = "--passphrase"; + capa["optional"]["passphrase"]["short"] = "P"; + capa["optional"]["passphrase"]["default"] = ""; + + opt.null(); + opt["long"] = "passphrase"; + opt["short"] = "P"; + opt["arg"] = "string"; + opt["default"] = ""; + opt["help"] = "If set, requires a SRT passphrase to connect"; + config->addOption("passphrase", opt); + + capa["optional"]["sockopts"]["name"] = "SRT socket options"; + capa["optional"]["sockopts"]["help"] = "Any additional SRT socket options to apply"; + capa["optional"]["sockopts"]["type"] = "string"; + capa["optional"]["sockopts"]["option"] = "--sockopts"; + capa["optional"]["sockopts"]["short"] = "O"; + capa["optional"]["sockopts"]["default"] = ""; + + opt.null(); + opt["long"] = "sockopts"; + opt["short"] = "O"; + opt["arg"] = "string"; + opt["default"] = ""; + opt["help"] = "Any additional SRT socket options to apply"; + config->addOption("sockopts", opt); + + } // Buffers TS packets and sends after 7 are buffered. void OutTSSRT::sendTS(const char *tsData, size_t len){ packetBuffer.append(tsData, len); if (packetBuffer.size() >= 1316){//7 whole TS packets - if (!srtConn){ + if (!*srtConn){ if (config->getString("target").size()){ + if (lastWorked + 5 < Util::bootSecs()){ + Util::logExitReason(ER_CLEAN_REMOTE_CLOSE, "SRT connection closed, no reconnect success after 5s"); + config->is_active = false; + parseData = false; + return; + } INFO_MSG("Reconnecting..."); - srtConn.connect(target.host, target.getPort(), "output", targetParams); - if (!srtConn){Util::sleep(500);} + if (srtConn){ + srtConn->close(); + delete srtConn; + } + if (udpInit){ + srtConn = new Socket::SRTConnection(*udpInit, "rendezvous", targetParams); + }else{ + srtConn = new Socket::SRTConnection(); + } + srtConn->connect(target.host, target.getPort(), "output", targetParams); + if (!*srtConn){Util::sleep(500);} }else{ - Util::logExitReason(ER_CLEAN_REMOTE_CLOSE, "SRT connection closed"); - myConn.close(); + Util::logExitReason(ER_CLEAN_REMOTE_CLOSE, "SRT connection closed (mid-send)"); + config->is_active = false; parseData = false; return; } } - if (srtConn){ - srtConn.SendNow(packetBuffer, packetBuffer.size()); - if (!srtConn){ + if (*srtConn){ + srtConn->SendNow(packetBuffer, packetBuffer.size()); + if (!*srtConn){ if (!config->getString("target").size()){ - Util::logExitReason(ER_CLEAN_REMOTE_CLOSE, "SRT connection closed"); - myConn.close(); + Util::logExitReason(ER_CLEAN_REMOTE_CLOSE, "SRT connection closed (post-send)"); + config->is_active = false; parseData = false; } + }else{ + lastWorked = Util::bootSecs(); } } packetBuffer.assign(0,0); @@ -346,11 +429,12 @@ namespace Mist{ } void OutTSSRT::requestHandler(){ - size_t recvSize = srtConn.Recv(); + size_t recvSize = srtConn->Recv(); if (!recvSize){ - if (!srtConn){ - myConn.close(); - srtConn.close(); + if (!*srtConn){ + Util::logExitReason(ER_CLEAN_REMOTE_CLOSE, "SRT connection %s (in request handler)", srtConn->getStateStr()); + config->is_active = false; + srtConn->close(); wantRequest = false; }else{ Util::sleep(50); @@ -358,13 +442,13 @@ namespace Mist{ return; } lastRecv = Util::bootSecs(); - if (!assembler.assemble(tsIn, srtConn.recvbuf, recvSize, true)){return;} + if (!assembler.assemble(tsIn, srtConn->recvbuf, recvSize, true)){return;} while (tsIn.hasPacket()){ tsIn.getEarliestPacket(thisPacket); if (!thisPacket){ - INFO_MSG("Could not get TS packet"); - myConn.close(); - srtConn.close(); + Util::logExitReason(ER_FORMAT_SPECIFIC, "Could not get TS packet"); + config->is_active = false; + srtConn->close(); wantRequest = false; return; } @@ -380,7 +464,7 @@ namespace Mist{ size_t thisIdx = M.trackIDToIndex(thisPacket.getTrackId(), getpid()); if (thisIdx == INVALID_TRACK_ID){return;} if (!userSelect.count(thisIdx)){ - userSelect[thisIdx].reload(streamName, thisIdx, COMM_STATUS_SOURCE | COMM_STATUS_DONOTTRACK); + userSelect[thisIdx].reload(streamName, thisIdx, COMM_STATUS_ACTIVE | COMM_STATUS_SOURCE | COMM_STATUS_DONOTTRACK); } uint64_t pktTimeWithOffset = thisPacket.getTime() + timeStampOffset; @@ -406,164 +490,126 @@ namespace Mist{ bool OutTSSRT::dropPushTrack(uint32_t trackId, const std::string & dropReason){ Util::logExitReason(ER_SHM_LOST, "track dropped by buffer"); - myConn.close(); - srtConn.close(); + config->is_active = false; + if (srtConn){srtConn->close();} return Output::dropPushTrack(trackId, dropReason); } void OutTSSRT::connStats(uint64_t now, Comms::Connections &statComm){ - if (!srtConn){return;} - statComm.setUp(srtConn.dataUp()); - statComm.setDown(srtConn.dataDown()); - statComm.setTime(now - srtConn.connTime()); - statComm.setPacketCount(srtConn.packetCount()); - statComm.setPacketLostCount(srtConn.packetLostCount()); - statComm.setPacketRetransmitCount(srtConn.packetRetransmitCount()); + if (!srtConn || !*srtConn){return;} + statComm.setUp(srtConn->dataUp()); + statComm.setDown(srtConn->dataDown()); + statComm.setTime(now - srtConn->connTime()); + statComm.setPacketCount(srtConn->packetCount()); + statComm.setPacketLostCount(srtConn->packetLostCount()); + statComm.setPacketRetransmitCount(srtConn->packetRetransmitCount()); } -}// namespace Mist - - -Socket::SRTServer server_socket; -static uint64_t sockCount = 0; - -void (*oldSignal)(int, siginfo_t *,void *) = 0; -void signal_handler(int signum, siginfo_t *sigInfo, void *ignore){ - server_socket.close(); - if (oldSignal){ - oldSignal(signum, sigInfo, ignore); + bool OutTSSRT::listenMode(){ + std::string tgt = config->getString("target"); + return (!tgt.size() || (tgt.size() >= 6 && tgt.substr(0, 6) == "srt://" && Socket::interpretSRTMode(HTTP::URL(tgt)) == "listener")); } -} -void handleUSR1(int signum, siginfo_t *sigInfo, void *ignore){ - if (!sockCount){ - INFO_MSG("USR1 received - triggering rolling restart (no connections active)"); - Util::Config::is_restarting = true; - Util::logExitReason(ER_CLEAN_SIGNAL, "signal USR1, no connections"); - server_socket.close(); - Util::Config::is_active = false; - }else{ - INFO_MSG("USR1 received - triggering rolling restart when connection count reaches zero"); - Util::Config::is_restarting = true; - Util::logExitReason(ER_CLEAN_SIGNAL, "signal USR1, after disconnect wait"); - } -} - -// Callback for SRT-serving threads -static void callThreadCallbackSRT(void *srtPtr){ - sockCount++; - Socket::SRTConnection & srtSock = *(Socket::SRTConnection*)srtPtr; - int fds[2]; - pipe(fds); - Socket::Connection Sconn(fds[0], fds[1]); - HIGH_MSG("Started thread for socket %i", srtSock.getSocket()); - mistOut tmp(Sconn,srtSock); - tmp.run(); - HIGH_MSG("Closing thread for socket %i", srtSock.getSocket()); - Sconn.close(); - srtSock.close(); - delete &srtSock; - sockCount--; - if (!sockCount && Util::Config::is_restarting){ - server_socket.close(); - Util::Config::is_active = false; - INFO_MSG("Last active connection closed; triggering rolling restart now!"); - } -} - -int main(int argc, char *argv[]){ - Socket::SRT::libraryInit(); - DTSC::trackValidMask = TRACK_VALID_EXT_HUMAN; - Util::redirectLogsIfNeeded(); - Util::Config conf(argv[0]); - Util::Config::binaryType = Util::OUTPUT; - mistOut::init(&conf); - if (conf.parseArgs(argc, argv)){ - if (conf.getBool("json")){ - mistOut::capa["version"] = PACKAGE_VERSION; - std::cout << mistOut::capa.toString() << std::endl; - return -1; + void initSRTConnection(Socket::UDPConnection & s, std::map & arguments, bool listener = true){ + Socket::SRT::libraryInit(); + Socket::SRTConnection * tmpSock = new Socket::SRTConnection(s, listener?"output":"rendezvous", arguments); + if (!*tmpSock){ + delete tmpSock; + return; } - conf.activate(); - - int filelimit = conf.getInteger("filelimit"); - Util::sysSetNrOpenFiles(filelimit); + if (!listener){ + std::string host; + uint32_t port; + s.GetDestination(host, port); + tmpSock->connect(host, port, "output", arguments); + INFO_MSG("UDP to SRT socket conversion: %s", tmpSock->getStateStr()); + } + Socket::Connection S(1, 0); + mistOut tmp(S, tmpSock); + tmp.run(); + } + void OutTSSRT::listener(Util::Config &conf, int (*callback)(Socket::Connection &S)){ + // Check SRT options/arguments first std::string target = conf.getString("target"); - if (!mistOut::listenMode() && (!target.size() || Socket::interpretSRTMode(HTTP::URL(target)) != "listener")){ - Socket::Connection S(fileno(stdout), fileno(stdin)); - Socket::SRTConnection tmpSock; - mistOut tmp(S, tmpSock); - return tmp.run(); - } - { - struct sigaction new_action; - new_action.sa_sigaction = handleUSR1; - sigemptyset(&new_action.sa_mask); - new_action.sa_flags = 0; - sigaction(SIGUSR1, &new_action, NULL); - } + std::map arguments; if (target.size()){ //Force acceptable option to 1 (outgoing only), since this is a push output and we can't accept incoming connections conf.getOption("acceptable", true).append((uint64_t)1); //Disable overriding streamname with streamid parameter on other side allowStreamNameOverride = false; HTTP::URL tgt(target); - std::map arguments; HTTP::parseVars(tgt.args, arguments); - server_socket = Socket::SRTServer(tgt.getPort(), tgt.host, arguments, false, "output"); + conf.getOption("interface", true).append(tgt.host); + conf.getOption("port", true).append((uint64_t)tgt.getPort()); conf.getOption("target", true).append(""); }else{ - std::map arguments; - server_socket = Socket::SRTServer(conf.getInteger("port"), conf.getString("interface"), arguments, false, "output"); + HTTP::parseVars(conf.getString("sockopts"), arguments); + std::string opt = conf.getString("passphrase"); + if (opt.size()){arguments["passphrase"] = opt;} } - if (!server_socket.connected()){ - DEVEL_MSG("Failure to open socket"); - return 1; + + uint16_t localPort; + // Either re-use socket 0 or bind a new socket + Socket::UDPConnection udpSrv; + if (Socket::checkTrueSocket(0)){ + udpSrv.assimilate(0); + localPort = udpSrv.getBoundPort(); + }else{ + localPort = udpSrv.bind(conf.getInteger("port"), conf.getString("interface")); } - struct sigaction new_action; - struct sigaction cur_action; - new_action.sa_sigaction = signal_handler; - sigemptyset(&new_action.sa_mask); - new_action.sa_flags = SA_SIGINFO; - sigaction(SIGINT, &new_action, &cur_action); - if (cur_action.sa_sigaction && cur_action.sa_sigaction != oldSignal){ - if (oldSignal){WARN_MSG("Multiple signal handlers! I can't deal with this.");} - oldSignal = cur_action.sa_sigaction; - } - sigaction(SIGHUP, &new_action, &cur_action); - if (cur_action.sa_sigaction && cur_action.sa_sigaction != oldSignal){ - if (oldSignal){WARN_MSG("Multiple signal handlers! I can't deal with this.");} - oldSignal = cur_action.sa_sigaction; - } - sigaction(SIGTERM, &new_action, &cur_action); - if (cur_action.sa_sigaction && cur_action.sa_sigaction != oldSignal){ - if (oldSignal){WARN_MSG("Multiple signal handlers! I can't deal with this.");} - oldSignal = cur_action.sa_sigaction; - } - Comms::defaultCommFlags = COMM_STATUS_NOKILL; - Util::Procs::socketList.insert(server_socket.getSocket()); - while (conf.is_active && server_socket.connected()){ - Socket::SRTConnection S = server_socket.accept(false, "output"); - if (S.connected()){// check if the new connection is valid - // spawn a new thread for this connection - tthread::thread T(callThreadCallbackSRT, (void *)new Socket::SRTConnection(S)); - // detach it, no need to keep track of it anymore - T.detach(); - }else{ - Util::sleep(10); // sleep 10ms + // Ensure socket zero is now us + if (udpSrv.getSock()){ + int oldSock = udpSrv.getSock(); + if (!dup2(oldSock, 0)){ + udpSrv.assimilate(0); } } - Util::Procs::socketList.erase(server_socket.getSocket()); - server_socket.close(); - if (conf.is_restarting){ - INFO_MSG("Reloading input..."); - execvp(argv[0], argv); - FAIL_MSG("Error reloading: %s", strerror(errno)); + if (!udpSrv){ + Util::logExitReason(ER_READ_START_FAILURE, "Failure to open listening socket"); + conf.is_active = false; + return; + } + Util::Config::setServerFD(0); + udpSrv.allocateDestination(); + + Util::Procs::socketList.insert(udpSrv.getSock()); + int maxFD = udpSrv.getSock(); + while (conf.is_active && udpSrv){ + + fd_set rfds; + FD_ZERO(&rfds); + FD_SET(maxFD, &rfds); + + struct timeval T; + T.tv_sec = 2; + T.tv_usec = 0; + int r = select(maxFD + 1, &rfds, NULL, NULL, &T); + if (r){ + while(udpSrv.Receive()){ + // Ignore if it's not an SRT handshake packet + if (udpSrv.data.size() >= 4 && udpSrv.data[0] == 0x80 && !udpSrv.data[1] && !udpSrv.data[2] && !udpSrv.data[3]){ + bool rendezvous = false; + if (udpSrv.data.size() >= 40){ + rendezvous = (!udpSrv.data[36] && !udpSrv.data[37] && !udpSrv.data[38] && !udpSrv.data[39]); + } + std::string remoteIP, localIP; + uint32_t remotePort, localPort; + udpSrv.GetDestination(remoteIP, remotePort); + udpSrv.GetLocalDestination(localIP, localPort); + INFO_MSG("SRT handshake from %s:%" PRIu32 "! Spawning child process to handle it...", remoteIP.c_str(), remotePort); + if (!fork()){ + Socket::UDPConnection s(udpSrv); + udpSrv.close(); + if (!s.connect()){return;} + return initSRTConnection(s, arguments, !rendezvous); + } + } + } + } } } - INFO_MSG("Exit reason: %s", Util::exitReason); - Socket::SRT::libraryCleanup(); - return 0; -} + +}// namespace Mist + diff --git a/src/output/output_tssrt.h b/src/output/output_tssrt.h index 9804c3e9..ce824fca 100644 --- a/src/output/output_tssrt.h +++ b/src/output/output_tssrt.h @@ -5,25 +5,29 @@ namespace Mist{ class OutTSSRT : public TSOutput{ public: - OutTSSRT(Socket::Connection &conn, Socket::SRTConnection & _srtSock); + OutTSSRT(Socket::Connection &conn, Socket::SRTConnection * _srtSock = 0); ~OutTSSRT(); - static bool listenMode(){return !(config->getString("target").size());} + static bool listenMode(); + static void listener(Util::Config &conf, int (*callback)(Socket::Connection &S)); static void init(Util::Config *cfg); void sendTS(const char *tsData, size_t len = 188); bool isReadyForPlay(){return true;} virtual void requestHandler(); virtual bool onFinish(); + virtual void initialSeek(bool dryRun = false); + inline virtual bool keepGoing(){return config->is_active;} protected: virtual void connStats(uint64_t now, Comms::Connections &statComm); - virtual std::string getConnectedHost(){return srtConn.remotehost;} - virtual std::string getConnectedBinHost(){return srtConn.getBinHost();} + virtual std::string getConnectedHost(){return srtConn?srtConn->remotehost:"";} + virtual std::string getConnectedBinHost(){return srtConn?srtConn->getBinHost():"";} virtual bool dropPushTrack(uint32_t trackId, const std::string & dropReason); private: HTTP::URL target; int64_t timeStampOffset; uint64_t lastTimeStamp; + uint64_t lastWorked; bool pushOut; Util::ResizeablePointer packetBuffer; Socket::UDPConnection pushSock; @@ -31,7 +35,8 @@ namespace Mist{ TS::Assembler assembler; bool bootMSOffsetCalculated; - Socket::SRTConnection & srtConn; + Socket::SRTConnection * srtConn; + Socket::UDPConnection * udpInit; }; }// namespace Mist From e981e26100d2ddc03cd8a8e430db9976c1c05c47 Mon Sep 17 00:00:00 2001 From: Thulinma Date: Wed, 4 Sep 2024 10:02:22 +0200 Subject: [PATCH 07/10] Update libsrt wrap to target v1.5.3 --- subprojects/libsrt.wrap | 2 +- subprojects/packagefiles/libsrt/meson.build | 20 ++++++++++++++----- .../packagefiles/libsrt/srt/meson.build | 19 +++++++++--------- 3 files changed, 25 insertions(+), 16 deletions(-) diff --git a/subprojects/libsrt.wrap b/subprojects/libsrt.wrap index 194571c5..f997ef46 100644 --- a/subprojects/libsrt.wrap +++ b/subprojects/libsrt.wrap @@ -1,7 +1,7 @@ [wrap-git] directory = srt url = https://github.com/Haivision/srt.git -revision = v1.5.1 +revision = v1.5.3 patch_directory = libsrt [provide] diff --git a/subprojects/packagefiles/libsrt/meson.build b/subprojects/packagefiles/libsrt/meson.build index 120cbd33..615461d7 100644 --- a/subprojects/packagefiles/libsrt/meson.build +++ b/subprojects/packagefiles/libsrt/meson.build @@ -1,9 +1,9 @@ -project('SRT', 'cpp', 'c', version: '1.5.1') +project('SRT', 'cpp', 'c', version: '1.5.3') if host_machine.system() == 'cygwin' - add_project_arguments(['-DENABLE_LOGGING=1', '-O3', '-DNDEBUG', '-DENABLE_MONOTONIC_CLOCK=1', '-DENABLE_NEW_RCVBUFFER=1', '-DENABLE_SOCK_CLOEXEC=1', '-DHAI_ENABLE_SRT=1', '-DHAI_PATCH=1', '-DHAVE_INET_PTON=1', '-DHAVE_PTHREAD_GETNAME_NP=1', '-DHAVE_PTHREAD_SETNAME_NP=1', '-DCYGWIN=1', '-DCYGWIN_USE_POSIX', '-DNDEBUG', '-DSRT_DYNAMIC', '-DSRT_ENABLE_APP_READER', '-DSRT_ENABLE_CLOSE_SYNCH', '-DSRT_ENABLE_ENCRYPTION', '-DSRT_EXPORTS', '-DSRT_VERSION="1.5.1"', '-DUSE_MBEDTLS=1', '-D_GNU_SOURCE'], language: ['cpp','c']) + add_project_arguments(['-DENABLE_LOGGING=1', '-O3', '-DNDEBUG', '-DENABLE_MONOTONIC_CLOCK=1', '-DENABLE_NEW_RCVBUFFER=1', '-DENABLE_SOCK_CLOEXEC=1', '-DHAI_ENABLE_SRT=1', '-DHAI_PATCH=1', '-DHAVE_INET_PTON=1', '-DHAVE_PTHREAD_GETNAME_NP=1', '-DHAVE_PTHREAD_SETNAME_NP=1', '-DCYGWIN=1', '-DCYGWIN_USE_POSIX', '-DNDEBUG', '-DSRT_DYNAMIC', '-DSRT_ENABLE_APP_READER', '-DSRT_ENABLE_CLOSE_SYNCH', '-DSRT_ENABLE_ENCRYPTION', '-DSRT_EXPORTS', '-DSRT_VERSION="1.5.3"', '-DUSE_MBEDTLS=1', '-D_GNU_SOURCE'], language: ['cpp','c']) else - add_project_arguments(['-DENABLE_LOGGING=1', '-O3', '-DNDEBUG', '-DENABLE_MONOTONIC_CLOCK=1', '-DENABLE_NEW_RCVBUFFER=1', '-DENABLE_SOCK_CLOEXEC=1', '-DHAI_ENABLE_SRT=1', '-DHAI_PATCH=1', '-DHAVE_INET_PTON=1', '-DHAVE_PTHREAD_GETNAME_NP=1', '-DHAVE_PTHREAD_SETNAME_NP=1', '-DLINUX=1', '-DNDEBUG', '-DSRT_DYNAMIC', '-DSRT_ENABLE_APP_READER', '-DSRT_ENABLE_BINDTODEVICE', '-DSRT_ENABLE_CLOSE_SYNCH', '-DSRT_ENABLE_ENCRYPTION', '-DSRT_EXPORTS', '-DSRT_VERSION="1.5.1"', '-DUSE_MBEDTLS=1', '-D_GNU_SOURCE'], language: ['cpp','c']) + add_project_arguments(['-DENABLE_LOGGING=1', '-O3', '-DNDEBUG', '-DENABLE_MONOTONIC_CLOCK=1', '-DENABLE_NEW_RCVBUFFER=1', '-DENABLE_SOCK_CLOEXEC=1', '-DHAI_ENABLE_SRT=1', '-DHAI_PATCH=1', '-DHAVE_INET_PTON=1', '-DHAVE_PTHREAD_GETNAME_NP=1', '-DHAVE_PTHREAD_SETNAME_NP=1', '-DLINUX=1', '-DNDEBUG', '-DSRT_DYNAMIC', '-DSRT_ENABLE_APP_READER', '-DSRT_ENABLE_BINDTODEVICE', '-DSRT_ENABLE_CLOSE_SYNCH', '-DSRT_ENABLE_ENCRYPTION', '-DSRT_EXPORTS', '-DSRT_VERSION="1.5.3"', '-DUSE_MBEDTLS=1', '-D_GNU_SOURCE'], language: ['cpp','c']) endif @@ -24,7 +24,8 @@ subdir('srt') srt_src = files( 'srtcore/api.cpp', - 'srtcore/buffer.cpp', + 'srtcore/buffer_snd.cpp', + 'srtcore/buffer_tools.cpp', 'srtcore/buffer_rcv.cpp', 'srtcore/cache.cpp', 'srtcore/channel.cpp', @@ -71,7 +72,8 @@ libsrt = library( 'srt', sources: [srt_src, versionfile], dependencies: [mbedtls_lib, mbedx509_lib, mbedcrypto_lib, thread_dep], - include_directories: ['srt', 'haicrypt', 'srtcore'] + include_directories: ['srt', 'haicrypt', 'srtcore'], + install: true ) srt_dep = declare_dependency( @@ -81,3 +83,11 @@ srt_dep = declare_dependency( include_directories: include_directories('.'), ) +pkg = import('pkgconfig') +pkg.generate(libraries : libsrt, + subdirs : ['.', 'srt'], + version : '1.5.3', + name : 'srt', + filebase : 'srt', + description : 'Haivision SRT library') + diff --git a/subprojects/packagefiles/libsrt/srt/meson.build b/subprojects/packagefiles/libsrt/srt/meson.build index dacabeca..5adb4c06 100644 --- a/subprojects/packagefiles/libsrt/srt/meson.build +++ b/subprojects/packagefiles/libsrt/srt/meson.build @@ -1,15 +1,14 @@ - versionfile = configure_file(format: 'cmake@', output: 'version.h', input: files('../srtcore/version.h.in'), configuration: { 'SRT_VERSION_MAJOR': 1, 'SRT_VERSION_MINOR' : 5, - 'SRT_VERSION_PATCH': 1, - 'CI_BUILD_NUMBER_STRING': '"1.5.1"', - 'SRT_VERSION': '1.5.1', -}) + 'SRT_VERSION_PATCH': 3, + 'CI_BUILD_NUMBER_STRING': '"1.5.3"', + 'SRT_VERSION': '1.5.3', +}, install_dir: 'include/srt') -header_tgts += configure_file(copy:true, input: files('../srtcore/srt.h'), output: 'srt.h') -header_tgts += configure_file(copy:true, input: files('../srtcore/logging_api.h'), output: 'logging_api.h') -header_tgts += configure_file(copy:true, input: files('../srtcore/access_control.h'), output: 'access_control.h') -header_tgts += configure_file(copy:true, input: files('../srtcore/platform_sys.h'), output: 'platform_sys.h') -header_tgts += configure_file(copy:true, input: files('../srtcore/udt.h'), output: 'udt.h') +header_tgts += configure_file(copy:true, install_dir: 'include/srt', input: files('../srtcore/srt.h'), output: 'srt.h') +header_tgts += configure_file(copy:true, install_dir: 'include/srt', input: files('../srtcore/logging_api.h'), output: 'logging_api.h') +header_tgts += configure_file(copy:true, install_dir: 'include/srt', input: files('../srtcore/access_control.h'), output: 'access_control.h') +header_tgts += configure_file(copy:true, install_dir: 'include/srt', input: files('../srtcore/platform_sys.h'), output: 'platform_sys.h') +header_tgts += configure_file(copy:true, install_dir: 'include/srt', input: files('../srtcore/udt.h'), output: 'udt.h') From 13845ca4fcfffc937fbb39a47fb726719abade69 Mon Sep 17 00:00:00 2001 From: Cat Date: Mon, 2 Sep 2024 11:46:16 +0200 Subject: [PATCH 08/10] LSP: - Bugfix for input options categories in checking if there are custom parameters in a push url - Option groups now have an ability to expand/collapse. When collapsed a summary of non-default configured options is shown. - Question marks in the target should be kept as part of the target url. Additional params should always be added after an additional ?. - Improved (mostly SRT) push param options layout - Fixed option groups: - 1: Typing in PUSH TARGET will reset all settings set up below - 2: If you hide settings the settings do not propogate - 3: Sublist and option categories share the same container class name, causing CSS collisions --- embed/min/player.js | 2 +- embed/min/wrappers/dashjs.js | 2 +- embed/min/wrappers/flash_strobe.js | 2 +- embed/min/wrappers/flv.js | 2 +- embed/min/wrappers/hlsjs.js | 2 +- embed/min/wrappers/html5.js | 2 +- embed/min/wrappers/mews.js | 2 +- embed/min/wrappers/rawws.js | 2 +- embed/min/wrappers/videojs.js | 2 +- embed/min/wrappers/webrtc.js | 2 +- lib/config.cpp | 89 ++++++++------ lsp/main.css | 62 ++++++++++ lsp/minified.js | 133 ++++++++++----------- lsp/mist.js | 183 +++++++++++++++-------------- src/output/output_tssrt.cpp | 152 +++++++++++++++--------- 15 files changed, 381 insertions(+), 258 deletions(-) diff --git a/embed/min/player.js b/embed/min/player.js index c5ea48bd..a26da177 100644 --- a/embed/min/player.js +++ b/embed/min/player.js @@ -1 +1 @@ -var MistUtil={format:{time:function(e,t){if(isNaN(e)||!isFinite(e))return e;t||(t={});var i=e<0?" ago":"";e=Math.abs(e);var r=Math.floor(e/86400);e-=86400*r;var n=Math.floor(e/3600);e-=3600*n;var a=Math.floor(e/60),s=Math.round(e%1*1e3);e=Math.floor(e-60*a);var o=[];return r&&(r=r+" day"+(r>1?"s":"")+", "),n||r?(o.push(n),o.push(("0"+a).slice(-2))):o.push(a),o.push(("0"+Math.floor(e)).slice(-2)),t.ms&&(o[o.length-1]+="."+("000"+s).slice(-3)),(r||"")+o.join(":")+i},ago:function(e,t){if(isNaN(e.getTime()))return"";var i=t||(new Date).getTime()-e.getTime(),r="",n=i<0;return n&&(i*=-1),i<1e3?r="live":i<6e4?(r=Math.round(i/1e3)+" sec",n?r="in "+r:r+=" ago"):r=!t&&(new Date).toLocaleDateString()==e.toLocaleDateString()||t<864e5?e.toLocaleTimeString(void 0,{hour:"numeric",minute:"2-digit",second:"2-digit"}):i<5184e5?e.toLocaleString(void 0,{weekday:"short",hour:"numeric",minute:"2-digit",second:"2-digit"}):!t&&(new Date).getFullYear()==e.getFullYear()||t<316224e5?e.toLocaleString(void 0,{month:"short",day:"numeric",weekday:"short",hour:"numeric",minute:"2-digit",second:"2-digit"}):e.toLocaleString(void 0,{year:"numeric",month:"short",day:"numeric",hour:"numeric",minute:"2-digit",second:"2-digit"}),r},ucFirst:function(e){return e.charAt(0).toUpperCase()+e.slice(1)},number:function(e){if(isNaN(Number(e))||0==Number(e))return e;var t=Math.max(3,Math.ceil(Math.log(e)/Math.LN10)),i=Math.pow(10,t-Math.floor(Math.log(e)/Math.LN10)-1);if((e=Math.round(e*i)/i)>=1e4){number=e.toString().split(".");for(var r=/(\d+)(\d{3})/;r.test(number[0]);)number[0]=number[0].replace(r,"$1 $2");e=number.join(".")}return e},bytes:function(e,t){if(isNaN(Number(e)))return e;var i=t?["bits","Kb","Mb","Gb","Tb","Pb"]:["bytes","KB","MB","GB","TB","PB"];if(0==e)unit=i[0];else{var r=Math.floor(Math.log(Math.abs(e))/Math.log(1024));r<0?unit=i[0]:(e/=Math.pow(1024,r),unit=i[r])}return this.number(e)+unit},bits:function(e){return this.bytes(e,!0)},mime2human:function(e){switch(e){case"html5/video/webm":return"WebM";case"html5/application/vnd.apple.mpegurl":return"HLS (TS)";case"html5/application/vnd.apple.mpegurl;version=7":return"HLS (CMAF)";case"flash/10":return"Flash (RTMP)";case"flash/11":return"Flash (HDS)";case"flash/7":return"Flash (Progressive)";case"html5/video/mpeg":return"TS";case"html5/application/vnd.ms-sstr+xml":case"html5/application/vnd.ms-ss":return"Smooth Streaming";case"dash/video/mp4":return"DASH";case"webrtc":return"WebRTC (WS)";case"whep":return"WebRTC (WHEP)";case"silverlight":return"Smooth streaming (Silverlight)";case"html5/text/vtt":return"VTT subtitles";case"html5/text/plain":return"SRT subtitles";default:return e.replace("html5/","").replace("video/","").replace("audio/","").toLocaleUpperCase()}}},class:{add:function(e,t){if("classList"in e)e.classList.add(t);else{var i=this.get(e);i.push(t),this.set(e,i)}},remove:function(e,t){if("classList"in e)e.classList.remove(t);else{for(var i=this.get(e),r=i.length-1;r>=0;r--)i[r]==t&&i.splice(r);this.set(e,i)}},get:function(e){var t=e.getAttribute("class");return t&&""!=t?t.split(" "):[]},set:function(e,t){e.setAttribute("class",t.join(" "))},has:function(e,t){return e.className.split(" ").indexOf(t)>=0}},object:{extend:function(e,t,i){for(var r in t)!i||"object"!=typeof t[r]||"nodeType"in t[r]?e[r]=t[r]:(r in e||(MistUtil.array.is(t[r])?e[r]=[]:e[r]={}),this.extend(e[r],t[r],!0));return e},keys:function(e,t){var i=[];for(var r in e)i.push(r);return t&&("function"!=typeof t&&(t=function(e,t){return e.localeCompare(t)}),i.sort(function(i,r){return t(i,r,e[i],e[r])})),i},values:function(e,t){var i=this.keys(e,t);for(var r in values=[],i)values.push(e[i[r]]);return values}},array:{indexOf:function(e,t){if(!(e instanceof Array))throw"Tried to use indexOf on something that is not an array";if("indexOf"in e)return e.indexOf(t);for(var i;it?1:e=0?r:i.length}if("function"==typeof e)return e(t);if("object"==typeof e){if(e instanceof Array)return i(t,e[0],e[1]);for(var r in e)return i(t,r,e[r])}if(e in t)return t[e];throw"Invalid sorting rule: "+e+". This should be a function, object or key of "+JSON.stringify(t)+"."}return e.sort(function(e,n){var a=0;for(var s in t){var o=t[s];if(0!=(a=i(r(o,e),r(o,n))))break}return a}),e}},createUnique:function(){var e="uid"+Math.random().toString().replace("0.","");return document.querySelector("."+e)?createUnique():e},http:{getpost:function(e,t,i,r,n){var a=new XMLHttpRequest;if(a.open(e,t,!0),"POST"==e&&a.setRequestHeader("Content-type","application/x-www-form-urlencoded"),n&&(a.timeout=8e3),a.onload=function(){var e=a.status;e>=200&&e<300?r(a.response):n&&n(a)},n&&(a.onerror=function(){n(a)},a.ontimeout=a.onerror),"POST"==e){var s,o=[];for(var l in i)o.push(l+"="+encodeURIComponent(i[l]));o.length&&(s=o.join("&")),a.send(s)}else a.send()},get:function(e,t,i){this.getpost("GET",e,null,t,i)},post:function(e,t,i,r){this.getpost("POST",e,t,i,r)},url:{addParam:function(e,t){var i=e.split("?"),r=[i.shift()],n=[];for(var a in i.length&&(n=i[0].split("&")),t)n.push(a+"="+t[a]);return n.length&&r.push(n.join("&")),r.join("?")},append:function(e,t){var i=document.createElement("a");return i.href=e,"?"==t[0]?""==i.search?i.search=t:i.search+="&"+t.slice(1):"&"==t[0]?""==i.search?i.search="?"+t.slice(1):i.search+=t:i.href+=t,i.href},split:function(e){var t=document.createElement("a");return t.href=e,{protocol:t.protocol,host:t.hostname,hash:t.hash,port:t.port,path:t.pathname.replace(/\/*$/,"")}},sanitizeHost:function(e){var t=MistUtil.http.url.split(e);return t.protocol+"//"+t.host+(t.port&&""!=t.port?":"+t.port:"")+(t.hash&&""!=t.hash?"#"+t.hash:"")+(t.path?"/"==t.path.charAt(0)?t.path:"/"+t.path:"")}}},css:{cache:{},load:function(e,t,i){var r=document.createElement("style");r.type="text/css",r.setAttribute("data-source",e),i&&(r.callback=i);var n=this.cache;function a(e){var i=MistUtil.css.applyColors(e,t);"callback"in r?r.callback(i):r.textContent=i}if(e in n)n[e]instanceof Array?n[e].push(a):a(n[e]);else{n[e]=[a];var s=3;!function t(){MistUtil.http.get(e,function(t){for(var i in n[e])n[e][i](t);n[e]=t},function(){if(s>0)s--,setTimeout(t,2e3);else{var i="/*Failed to load*/";for(var r in n[e])n[e][r](i);n[e]=i}})}()}return r},applyColors:function(e,t){return e.replace(/\$([^\s^;^}]*)/g,function(e,i){var r=i.split("."),n=t;for(var a in r)n=n[r[a]];return n})},createStyle:function(e,t,i){var r=document.createElement("style");return r.type="text/css",e&&(t&&(e=this.prependClass(e,t,i)),r.textContent=e),r},prependClass:function(e,t,i){var r=!1;"string"!=typeof e&&("unprepended"in(r=e)||(r.unprepended=r.textContent),e=r.unprepended);var n=(e=e.replace(/\/\*.*?\*\//g,"")).match(/@[^}]*}/g);for(var a in n){e=e.replace(n[a],"@@#@@");for(var s=1;s0)s=r.bps>131072?Math.round(r.bps/1024/1024*8)+"mbps":Math.round(r.bps/1024*8)+"kbps",n[a]=s;break;case"fpks":r.fpks>0&&(n[a]=r.fpks/1e3+"fps");break;case"channels":r.channels>0&&(n[a]=1==r.channels?"Mono":2==r.channels?"Stereo":"Surround ("+r.channels+"ch)");break;case"rate":n[a]=Math.round(.001*r.rate)+"Khz";break;case"language":"Undetermined"!=r[a]&&(n[a]=r[a]);break;case"codec":if("meta"==r.codec)continue;n[a]=r[a]}r.describe=n}for(var o in t){var l=!1;for(var i in t[o])if(l){if(MistUtil.object.keys(t[o]).length>1)for(var a in t[o][i].describe)l[a]!=t[o][i].describe[a]&&delete l[a]}else l=MistUtil.object.extend({},t[o][i].describe);for(var i in t[o]){var c={},d={};for(var a in t[o][i].describe)a in l?d[a]=t[o][i].describe[a]:c[a]=t[o][i].describe[a];t[o][i].different=c,t[o][i].same=d;var u=MistUtil.object.values(c);t[o][i].displayName=u.length?u.join(", "):MistUtil.object.values(t[o][i].describe).join(" ")}var p={};for(var i in t[o]){if(t[o][i].displayName in p){var h=1;for(var i in t[o])t[o][i].different.trackid=h+")",t[o][i].displayName="Track "+h+" ("+t[o][i].displayName+")",h++;break}p[t[o][i].displayName]=1}}return t},translateCodec:function(e){function t(t){return("0"+e.init.charCodeAt(t).toString(16)).slice(-2)}switch(e.codec){case"AAC":return"mp4a.40.2";case"MP3":return"mp3";case"AC3":return"ec-3";case"H264":return"avc1."+t(1)+t(2)+t(3);case"HEVC":return"hev1."+t(1)+t(6)+t(7)+t(8)+t(9)+t(10)+t(11)+t(12);default:return e.codec.toLowerCase()}}},isTouchDevice:function(){return"ontouchstart"in window||navigator.msMaxTouchPoints>0},getPos:function(e,t){e.currentStyle||window.getComputedStyle(e,null);for(var i=1,r=e;r;)r.style.zoom&&""!=r.style.zoom&&(i*=parseFloat(r.style.zoom,10)),r=r.parentElement;var n=e.getBoundingClientRect().left-(parseInt(e.borderLeftWidth,10)||0),a=e.getBoundingClientRect().width,s=Math.max(0,(t.clientX/i-n)/a);return s=Math.min(s,1)},createGraph:function(e,t){var i="http://www.w3.org/2000/svg",r=document.createElementNS(i,"svg");r.setAttributeNS(null,"height","100%"),r.setAttributeNS(null,"width","100%"),r.setAttributeNS(null,"class","mist icon graph"),r.setAttributeNS(null,"preserveAspectRatio","none");var n=e.x[0],a=e.y[0];if(t.differentiate)for(var s=1;st.x.count&&(l.shift(),d()),d(e.x-n,-1*e.y),this.setAttributeNS(null,"d","M"+l.join(" L")),h()}},r.addData=function(e){m.addData(e)},r},getBrowser:function(){var e=window.navigator.userAgent;return e.indexOf("MSIE ")>=0||e.indexOf("Trident/")>=0?"ie":e.indexOf("Edge/")>=0?"edge":e.indexOf("Opera")>=0||e.indexOf("OPR")>=0?"opera":e.indexOf("Chrome")>=0?"chrome":e.indexOf("Safari")>=0?"safari":e.indexOf("Firefox")>=0&&"firefox"},getAndroid:function(){var e=navigator.userAgent.toLowerCase().match(/android\s([\d\.]*)/i);return!!e&&e[1]},sources:{find:function(e,t){e:for(var i in e){for(var r in t)if("protocol"==r){if(e[i].url.slice(0,t.protocol.length)!=t.protocol)continue e}else if(e[i][r]!=t[r])continue e;return e[i]}return!1}}};if(void 0===MistSkins)var MistSkins={};if("undefined"!=typeof mistoptions&&"host"in mistoptions)var misthost=MistUtil.http.url.sanitizeHost(mistoptions.host);else misthost="..";function MistSkin(e){e.skin=this,this.applySkinOptions=function(t){var i;return"string"==typeof t&&t in MistSkins&&(t=MistUtil.object.extend({},MistSkins[t],!0)),i="inherit"in t&&t.inherit&&t.inherit in MistSkins?this.applySkinOptions(t.inherit):MistSkins.default,this.structure=MistUtil.object.extend({},i.structure),t&&"structure"in t&&MistUtil.object.extend(this.structure,t.structure),this.blueprints=MistUtil.object.extend({},i.blueprints),t&&"blueprints"in t&&MistUtil.object.extend(this.blueprints,t.blueprints),this.icons=MistUtil.object.extend({},i.icons,!0),t&&"icons"in t&&MistUtil.object.extend(this.icons.blueprints,t.icons),this.icons.build=function(t,i,r){i||(i=22);var n,a=this.blueprints[t];n="function"==typeof a.svg?a.svg.call(e,r):a.svg,"object"!=typeof i&&(i={height:i,width:i}),"object"!=typeof a.size&&(a.size={height:a.size,width:a.size}),(!("width"in i)&&"height"in i||!("height"in i)&&"width"in i)&&("width"in i&&(i.height=i.width*a.size.height/a.size.width),"height"in i&&(i.width=i.height*a.size.width/a.size.height));var s="";s+='',s+='',s+=n,s+="",s+="";var o=document.createElement("div");return o.innerHTML=s,o.firstChild},this.colors=MistUtil.object.extend({},i.colors),t&&"colors"in t&&MistUtil.object.extend(this.colors,t.colors,!0),this.css=MistUtil.object.extend({},i.css),t&&"css"in t&&MistUtil.object.extend(this.css,t.css),this},this.applySkinOptions("skin"in e.options?e.options.skin:"default");var t=[];for(var i in this.css)if("string"==typeof this.css[i]){var r=MistUtil.css.load(e.urlappend(this.css[i]),this.colors);t.push(r)}this.css=t}function MistUI(e,t){e.UI=this,this.elements=[],this.buildStructure=function(t){if("function"==typeof t&&(t=t.call(e)),"if"in t){var i=!1;if(t.if.call(e,t)?i=t.then:"else"in t&&(i=t.else),!i)return;for(var r in t)["if","then","else"].indexOf(r)<0&&(r in i?(i[r]instanceof Array||(i[r]=[i[r]]),i[r]=i[r].concat(t[r])):i[r]=t[r]);return this.buildStructure(i)}if("type"in t&&t.type in e.skin.blueprints){var n=e.skin.blueprints[t.type].call(e,t);if(!n)return;if(MistUtil.class.add(n,"mistvideo-"+t.type),"css"in t){var a=MistUtil.createUnique();for(var r in t.css=[].concat(t.css),t.css){var s=MistUtil.css.createStyle(t.css[r],a);n.appendChild(s)}MistUtil.class.add(n,a),n.uid=a}if("classes"in t)for(var r in t.classes)MistUtil.class.add(n,t.classes[r]);if("title"in t&&(n.title=t.title),"style"in t)for(var r in t.style)n.style[r]=t.style[r];if("children"in t)for(var r in t.children){var o=this.buildStructure(t.children[r]);o&&n.appendChild(o)}return e.UI.elements.push(n),n}return!1},this.build=function(){return this.buildStructure(t||e.skin.structure.main)};var i=this.build(),r=MistUtil.createUnique(),n=0;for(var a in e.skin.css.length&&(i.style.opacity=0),e.skin.css){var s=e.skin.css[a];s.callback=function(t){"/*Failed to load*/"==t?(this.textContent=t,e.showError("Failed to load CSS from "+this.getAttribute("data-source"))):this.textContent=MistUtil.css.prependClass(t,r,!0),n++,e.skin.css.length<=n&&(i.style.opacity="")},""!=s.textContent&&s.callback(s.textContent),i.appendChild(s)}MistUtil.class.add(i,r);var o=MistUtil.getBrowser();return o&&MistUtil.class.add(i,"browser-"+o),i}MistSkins.default={structure:{main:{if:function(){return!!this.info.hasVideo&&"audio"!=this.source.type.split("/")[1]},then:{type:"placeholder",classes:["mistvideo"],children:[{type:"hoverWindow",classes:["mistvideo-maincontainer"],mode:"pos",style:{position:"relative"},transition:{hide:"left: 0; right: 0; bottom: -43px;",show:"bottom: 0;",viewport:"left:0; right: 0; top: -1000px; bottom: 0;"},button:{type:"videocontainer"},children:[{type:"loading"},{type:"error"}],window:{type:"controls"}}]},else:{type:"container",classes:["mistvideo"],style:{overflow:"visible"},children:[{type:"controls",classes:["mistvideo-novideo"],style:{width:"480px"}},{type:"loading"},{type:"error"},{if:function(){return"stock"==this.options.controls},then:{type:"video",style:{position:"absolute"}},else:{type:"video",style:{position:"absolute",display:"none"}}}]}},videocontainer:{type:"container",children:[{type:"videobackground",alwaysDisplay:!1,delay:5},{type:"video"},{type:"subtitles"}]},controls:{if:function(){return!!(this.player&&this.player.api&&this.player.api.play)},then:{type:"container",classes:["mistvideo-column"],children:[{type:"progress",classes:["mistvideo-pointer"]},{type:"container",classes:["mistvideo-main","mistvideo-padding","mistvideo-row","mistvideo-background"],children:[{type:"play",classes:["mistvideo-pointer"]},{type:"currentTime"},{if:function(){return"size"in this&&this.size.width>300||!this.info.hasVideo||"audio"==this.source.type.split("/")[1]},then:{type:"totalTime"}},{type:"container",classes:["mistvideo-align-right"],children:[{type:"container",children:[{type:"container",classes:["mistvideo-volume_container"],children:[{type:"volume",mode:"horizontal",size:{height:22},classes:["mistvideo-pointer"]}]},{type:"speaker",classes:["mistvideo-pointer"],style:{"margin-left":"-2px"}}]},{if:function(){return"size"in this&&this.size.width>300||!this.info.hasVideo||"audio"==this.source.type.split("/")[1]},then:{type:"container",children:[{type:"chromecast",classes:["mistvideo-pointer"]},{type:"loop",classes:["mistvideo-pointer"]},{type:"fullscreen",classes:["mistvideo-pointer"]},{type:"picture-in-picture",classes:["mistvideo-pointer"]}]}},{type:"hoverWindow",mode:"pos",transition:{hide:"right: -1000px; bottom: 44px;",show:"right: 5px;",viewport:"right: 0; left: 0; bottom: 0; top: -1000px"},button:{type:"settings",classes:["mistvideo-pointer"]},window:{type:"submenu"}}]}]}]},else:{if:function(){return!(!this.player||!this.player.api)},then:{type:"hoverWindow",mode:"pos",transition:{hide:"right: -1000px; bottom: 44px;",show:"right: 2.5px;",viewport:"right: 0; left: -1000px; bottom: 0; top: -1000px"},style:{right:"5px",left:"auto"},button:{type:"settings",classes:["mistvideo-background","mistvideo-padding"]},window:{type:"submenu"}}}},submenu:{type:"container",style:{width:"80%",maxWidth:"25em",zIndex:2},classes:["mistvideo-padding","mistvideo-column","mistvideo-background"],children:[{type:"tracks"},{if:function(){return"size"in this&&this.size.width<=300},then:{type:"container",classes:["mistvideo-center"],children:[{type:"chromecast",classes:["mistvideo-pointer"]},{type:"loop",classes:["mistvideo-pointer"]},{type:"fullscreen",classes:["mistvideo-pointer"]},{type:"picture-in-picture",classes:["mistvideo-pointer"]}]}}]},placeholder:{type:"container",classes:["mistvideo","mistvideo-delay-display"],children:[{type:"placeholder"},{type:"loading"},{type:"error"}]},secondaryVideo:function(e){return{type:"hoverWindow",classes:["mistvideo"],mode:"pos",transition:{hide:"left: 10px; bottom: -40px;",show:"bottom: 10px;",viewport:"left: 0; right: 0; top: 0; bottom: 0"},button:{type:"container",children:[{type:"videocontainer"}]},window:{type:"switchVideo",classes:["mistvideo-controls","mistvideo-padding","mistvideo-background","mistvideo-pointer"],containers:e}}}},css:{skin:misthost+"/skins/default.css"},icons:{blueprints:{play:{size:45,svg:''},largeplay:{size:45,svg:''},pause:{size:45,svg:''},speaker:{size:45,svg:''},volume:{size:{width:100,height:45},svg:function(){var e=MistUtil.createUnique();return''}},muted:{size:45,svg:''},fullscreen:{size:45,svg:''},pip:{size:45,svg:''},loop:{size:45,svg:''},settings:{size:45,svg:''},loading:{size:100,svg:''},timeout:{size:25,svg:function(e){e&&e.delay||(e={delay:10});var t=e.delay,i=MistUtil.createUnique();return''}},popout:{size:45,svg:''},switchvideo:{size:45,svg:''}}},blueprints:{container:function(){return document.createElement("div")},video:function(){var e=this;if(MistUtil.event.addListener(e.video,"contextmenu",function(t){t.preventDefault(),e.container.setAttribute("data-show-submenu",""),e.container.removeAttribute("data-hide-submenu"),e.container.removeAttribute("data-hidecursor");var i=function(){e.container.removeAttribute("data-show-submenu"),e.container.removeEventListener("mouseout",i)};MistUtil.event.addListener(e.container,"mouseout",i)}),e.video.hideTimer=!1,e.video.hideCursor=function(){this.hideTimer&&clearTimeout(this.hideTimer),this.hideTimer=e.timers.start(function(){e.container.setAttribute("data-hidecursor","");var t=e.container.querySelector(".mistvideo-controls");t&&t.parentNode.setAttribute("data-hidecursor","")},3e3)},MistUtil.event.addListener(e.video,"mousemove",function(){e.container.removeAttribute("data-hidecursor");var t=e.container.querySelector(".mistvideo-controls");t&&t.parentNode.removeAttribute("data-hidecursor"),e.video.hideCursor()}),MistUtil.event.addListener(e.video,"mouseout",function(){e.video.hideTimer&&e.timers.stop(e.video.hideTimer)}),e.options.autoplay)var t=MistUtil.event.addListener(e.video,"canplay",function(){if(e.player.api&&e.player.api.paused){var i=e.player.api.play();i&&i.catch(function(t){if(!e.destroyed)if(e.log("Autoplay failed. Retrying with muted audio.."),e.info.hasVideo){e.player.api.muted=!0,MistUtil.event.send("volumechange",null,e.video);var i=e.player.api.play();i&&i.then(function(){e.reporting&&(e.reporting.stats.d.autoplay="success")}).then(function(){if(!e.destroyed){e.log("Autoplay worked! Video will be unmuted on mouseover if the page has been interacted with."),e.reporting&&(e.reporting.stats.d.autoplay="muted");var t=e.skin.icons.build("muted",100);MistUtil.class.add(t,"mistvideo-pointer"),e.container.appendChild(t),MistUtil.event.addListener(t,"click",function(){e.player.api.muted=!1,e.container.removeChild(t)});var i=!1,r=function(){i=!0,document.body.removeEventListener("click",r)};MistUtil.event.addListener(document.body,"click",r,e.video);var n=function(){i&&(e.player.api.muted=!1,e.video.removeEventListener("mouseenter",n),e.log("Re-enabled sound"))};MistUtil.event.addListener(e.video,"mouseenter",n);var a=function(){e.player.api.muted||(t.parentNode&&e.container.removeChild(t),e.video.removeEventListener("volumechange",a),document.body.removeEventListener("click",r),e.video.removeEventListener("mouseenter",n))};MistUtil.event.addListener(e.video,"volumechange",a)}}).catch(function(){if(!e.destroyed){e.log("Autoplay failed even with muted video. Unmuting and showing play button."),e.timers.start(function(){e.player.api.paused&&(e.player.api.pause(),e.monitor&&e.monitor.destroy())},5e3),e.reporting&&(e.reporting.stats.d.autoplay="failed"),e.player.api.muted=!1;var t=e.skin.icons.build("largeplay",150);MistUtil.class.add(t,"mistvideo-pointer"),e.container.appendChild(t),MistUtil.event.addListener(t,"click",function(){e.player.api.paused&&e.player.api.play()});var i=function(){e.container.removeChild(t),e.video.removeEventListener("play",i)};MistUtil.event.addListener(e.video,"play",i)}})}else e.reporting&&(e.reporting.stats.d.autoplay="failed")})}else e.reporting&&(e.reporting.stats.d.autoplay="success");MistUtil.event.removeListener(t)});return this.video},videocontainer:function(){return this.UI.buildStructure(this.skin.structure.videocontainer)},secondaryVideo:function(e){e||(e={}),e.options||(e.options={});var t=this;"secondary"in t||(t.secondary=[]);var i=MistUtil.object.extend({},t.options);i=MistUtil.object.extend(i,e.options),t.secondary.push(i);var r={primary:t,secondary:!1};i.target=document.createElement("div"),delete i.container;var n={};return i.MistVideoObject=n,MistUtil.event.addListener(i.target,"initialized",function(){var e=n.reference;i.MistVideo=e,r.secondary=e,e.player.api.muted=!0,e.player.api.loop=!1;for(var a=i.target.querySelectorAll(".mistvideo-controls"),s=0;s30)e.player.api.pausedesync=!0,e.player.api.currentTime=this.currentTime,e.log("Re-syncing with main video by seeking (desync: "+t+"s)");else if(i>.01){var r=.1;i<1&&(r=.05),(r=1+r*Math.sign(t))!=e.player.api.playbackRate&&e.log("Re-syncing by changing the playback rate (desync: "+Math.round(1e3*t)+"ms, rate: "+r+")"),e.player.api.playbackRate=r}else 1!=e.player.api.playbackRate&&(e.player.api.playbackRate=1,e.log("Sync with main video achieved (desync: "+Math.round(1e3*t)+"ms)"))}},i.target),MistUtil.event.addListener(e.video,"seeked",function(){e.player.api.pausedesync=!1})}),i.skin=MistUtil.object.extend({},t.skin,!0),i.skin.structure.main=MistUtil.object.extend({},t.skin.structure.secondaryVideo(r)),mistPlay(t.stream,i),i.target},switchVideo:function(e){var t=document.createElement("div");return t.appendChild(this.skin.icons.build("switchvideo")),MistUtil.event.addListener(t,"click",function(){var t=e.containers.primary,i=e.containers.secondary;function r(e,t){if(e.video.currentTarget==t)return e.video;if(e.secondary)for(var i=0;i300&&(e.style.zoom=1.5),e}},submenu:function(){return this.UI.buildStructure(this.skin.structure.submenu)},hoverWindow:function(e){var t={type:"container",classes:"classes"in e?e.classes:[],children:"children"in e?e.children:[]};switch(t.classes.push("hover_window_container"),"classes"in e.window||(e.window.classes=[]),e.window.classes.push("inner_window"),e.window.classes.push("mistvideo-container"),e.window={type:"container",classes:["outer_window"],children:[e.window]},"classes"in e.button||(e.button.classes=[]),e.button.classes.push("pointer"),e.mode){case"left":t.classes.push("horizontal"),t.children=[e.window,e.button];break;case"right":t.classes.push("horizontal"),t.children=[e.button,e.window];break;case"top":t.classes.push("vertical"),t.children=[e.button,e.window];break;case"bottom":t.classes.push("vertical"),t.children=[e.window,e.button];break;case"pos":t.children=[e.button,e.window],"classes"in e.window||(e.window.classes=[]);break;default:throw"Unsupported mode for structure type hoverWindow"}return"transition"in e&&("css"in t||(t.css=[]),t.css.push(".hover_window_container:hover > .outer_window:not([data-hidecursor]) > .inner_window { "+e.transition.show+" }\n.hover_window_container > .outer_window { "+e.transition.viewport+" }\n.hover_window_container > .outer_window > .inner_window { "+e.transition.hide+" }")),t.classes.push(e.mode),this.UI.buildStructure(t)},draggable:function(e){var t=this.skin.blueprints.container(e),i=this,r=this.skin.icons.build("fullscreen",16);MistUtil.class.remove(r,"fullscreen"),MistUtil.class.add(r,"draggable-icon"),t.appendChild(r),r.style.alignSelf="flex-end",r.style.position="absolute",r.style.cursor="move";var n={},a=function(e){t.style.left=e.clientX-n.x+"px",t.style.top=e.clientY-n.y+"px"},s=function(e){window.removeEventListener("mousemove",a),window.removeEventListener("click",s),MistUtil.event.addListener(r,"click",o)},o=function(e){e.stopPropagation(),r.removeEventListener("click",o),n.x=i.container.getBoundingClientRect().left-(t.getBoundingClientRect().left-e.clientX),n.y=i.container.getBoundingClientRect().top-(t.getBoundingClientRect().top-e.clientY),t.style.position="absolute",t.style.right="auto",t.style.bottom="auto",i.container.appendChild(t),a(e),MistUtil.event.addListener(window,"mousemove",a,t),MistUtil.event.addListener(window,"click",s,t)};return MistUtil.event.addListener(r,"click",o),t},progress:function(){var e=document.createElement("div"),t=document.createElement("div");e.appendChild(t),t.kids={},t.kids.bar=document.createElement("div"),t.kids.bar.className="bar",t.appendChild(t.kids.bar);var i=this.video,r=this,n=1/0;if(r.info&&r.info.meta&&r.info.meta.tracks)for(var a in r.info.meta.tracks).001*r.info.meta.tracks[a].firstms1e3?(t.updateBuffers(r.player.api.buffered),l=(new Date).getTime()):c||(c=r.timers.start(function(){e(),c=!1},1e3))}()},t);var d=0,u=!1;MistUtil.event.addListener(i,"timeupdate",function(){!function e(){(new Date).getTime()-d>200&&!f?(t.updateBar(r.player.api.currentTime),d=(new Date).getTime()):u||(u=r.timers.start(function(){e(),u=!1},1e3))}()},t),MistUtil.event.addListener(i,"seeking",function(){t.updateBar(r.player.api.currentTime)},t),t.getPos=function(e){var t=isNaN(e)?MistUtil.getPos(this,e):e;return"live"==r.info.type?(t-1)*o()+r.player.api.duration:!!isFinite(r.player.api.duration)&&t*(r.player.api.duration-s())+s()},t.seek=function(e){var t=this.getPos(e);r.player.api.currentTime=t},MistUtil.event.addListener(e,"mouseup",function(e){1==e.which&&t.seek(e)});var p=r.UI.buildStructure({type:"tooltip"});p.style.opacity=0,t.appendChild(p),MistUtil.event.addListener(e,"mouseout",function(){f||(p.style.opacity=0)}),t.moveTooltip=function(e){var t=this.getPos(e);if(!1!==t){p.setDisplay(t),p.style.opacity=1;var i=MistUtil.getPos(this,e),r={bottom:20};i>.5?(r.right=100*(1-i)+"%",p.triangle.setMode("bottom","right")):(r.left=100*i+"%",p.triangle.setMode("bottom","left")),p.setPos(r)}else p.style.opacity=0};var h=document.createElement("span");h.setAttribute("class","mistvideo-realtime");var m=document.createTextNode("");h.appendChild(m),p.setDisplay=function(e){if(r.options.useDateTime&&r.info&&r.info.unixoffset){var i=t.getPos(1)-t.getPos(0),n=.001*(new Date).getTime()-(.001*r.info.unixoffset+t.getPos(1)),a=Math.max(i,n),s="";if("live"==r.info.type)if(a<60)s=MistUtil.format.ago(new Date(r.info.unixoffset+1e3*e));else{var o=.001*(new Date).getTime()-(.001*r.info.unixoffset+e);o<172800&&(s+=" - "+MistUtil.format.time(o))}else s+=MistUtil.format.time(e);if(a>=60){m.nodeValue=" at "+MistUtil.format.ago(new Date(r.info.unixoffset+1e3*e),1e3*a);var l=document.createDocumentFragment();l.appendChild(document.createTextNode(s)),l.appendChild(h),p.setHtml(l)}else m.nodeValue="",p.setText(s)}else p.setText(MistUtil.format.time(e))},MistUtil.event.addListener(e,"mousemove",function(e){t.moveTooltip(e)});var f=!1;return MistUtil.event.addListener(e,"mousedown",function(i){if(1==i.which){f=!0,t.updateBar(t.getPos(i));var r=MistUtil.event.addListener(document,"mousemove",function(e){t.updateBar(t.getPos(e)),t.moveTooltip(e)},t),n=MistUtil.event.addListener(document,"mouseup",function(i){1==i.which&&(f=!1,MistUtil.event.removeListener(r),MistUtil.event.removeListener(n),p.style.opacity=0,(!i.path||MistUtil.array.indexOf(i.path,e)<0)&&t.seek(i))},t)}}),e},play:function(){var e=this,t=document.createElement("div");t.appendChild(this.skin.icons.build("play")),t.appendChild(this.skin.icons.build("pause")),t.setState=function(e){this.setAttribute("data-state",e)},t.setState("paused");var i=this.video;return MistUtil.event.addListener(i,"playing",function(){t.setState("playing"),e.options.autoplay=!0},t),MistUtil.event.addListener(i,"pause",function(){t.setState("paused")},t),MistUtil.event.addListener(i,"paused",function(){t.setState("paused")},t),MistUtil.event.addListener(i,"ended",function(){t.setState("paused")},t),MistUtil.event.addListener(t,"click",function(){e.player.api.error&&e.player.api.load(),e.player.api.paused?e.player.api.play():(e.player.api.pause(),e.options.autoplay=!1)}),e.player.api&&MistUtil.event.addListener(e.video,"click",function(){e.player.api.paused?e.player.api.play():MistUtil.isTouchDevice()||(e.player.api.pause(),e.options.autoplay=!1)},t),t},speaker:function(){if(!(this.player.api&&"muted"in this.player.api))return!1;var e=!1,t=this.info.meta.tracks;for(var i in t)if("audio"==t[i].type){e=!0;break}if(!e)return!1;var r=this.skin.icons.build("speaker"),n=this,a=this.video;return n.player.api.volume&&!n.player.api.muted||MistUtil.class.add(r,"off"),MistUtil.event.addListener(a,"volumechange",function(){n.player.api.volume&&!n.player.api.muted?MistUtil.class.remove(r,"off"):MistUtil.class.add(r,"off")},r),MistUtil.event.addListener(r,"click",function(e){n.player.api.muted=!n.player.api.muted}),r},volume:function(e){if(!(this.player.api&&"volume"in this.player.api))return!1;var t=!1,i=this.info.meta.tracks;for(var r in i)if("audio"==i[r].type){t=!0;break}if(!t)return!1;var n=document.createElement("div"),a=this.skin.icons.build("volume","size"in e&&e.size);n.appendChild(a);var s=this;a.mode="mode"in e?e.mode:"vertical","vertical"==a.mode&&(a.style.transform="rotate(90deg)"),a.margin={start:.15,end:.1};var o=this.video;a.set=function(e){100!=(e=100-100*Math.pow(1-e/100,2))&&0!=e&&(e=100*this.addPadding(e/100));for(var t=a.querySelectorAll(".slider"),i=0;i6e4&&e.size.width>=600&&(n.nodeValue=" (at "+MistUtil.format.ago(o)+")")),t.setAttribute("title",MistUtil.format.ago(o,3456e7))}else r=a(s),t.setAttribute("title",r);i.nodeValue=r},t.set(),MistUtil.event.addListener(e.video,"timeupdate",function(){t.set()},t),MistUtil.event.addListener(e.video,"seeking",function(){t.set()},t),t},totalTime:function(){var e=this,t=document.createElement("div"),i=document.createTextNode("");t.appendChild(i);this.player.api;return"live"==e.info.type?(i.nodeValue="live",t.className="live"):(t.set=function(r){if(!isNaN(r)&&isFinite(r))if(this.style.display="",e.options.useDateTime&&e.info&&"live"==e.info.type&&e.info.unixoffset){var n=new Date(1e3*r+e.info.unixoffset);i.nodeValue=MistUtil.format.ago(n),t.setAttribute("title",MistUtil.format.ago(n,3456e7))}else i.nodeValue=MistUtil.format.time(r),t.setAttribute("title",i.nodeValue);else this.style.display="none"},MistUtil.event.addListener(e.video,"durationchange",function(){var i=e.player.api.duration;t.set(i)},t)),t},playername:function(){if(this.playerName&&this.playerName in mistplayers){var e=document.createElement("span");return e.appendChild(document.createTextNode(mistplayers[this.playerName].name)),e}},mimetype:function(){if(this.source){var e=document.createElement("a");return e.href=this.source.url,e.target="_blank",e.title=e.href+" ("+this.source.type+")",e.appendChild(document.createTextNode(MistUtil.format.mime2human(this.source.type))),e}},logo:function(e){if("element"in e)return e.element;if("src"in e){var t=document.createElement("img");return t.src=e.src,t}},settings:function(){var e=this,t=this.skin.icons.build("settings"),i=void 0!==document.ontouchstart;return MistUtil.event.addListener(t,"click",function(){e.container.hasAttribute("data-show-submenu")?(i&&e.container.setAttribute("data-hide-submenu",""),e.container.removeAttribute("data-show-submenu")):(e.container.setAttribute("data-show-submenu",""),e.container.removeAttribute("data-hide-submenu"))}),t},loop:function(){if("loop"in this.player.api&&"live"!=this.info.type){var e=this,t=this.skin.icons.build("loop");this.video;return t.set=function(){e.player.api.loop?MistUtil.class.remove(this,"off"):MistUtil.class.add(this,"off")},MistUtil.event.addListener(t,"click",function(t){e.player.api.loop=!e.player.api.loop,this.set()}),t.set(),t}},fullscreen:function(){if("setSize"in this.player&&this.info.hasVideo&&"audio"!=this.source.type.split("/")[1]){var e=this,t=["requestFullscreen","webkitRequestFullscreen","mozRequestFullScreen","msRequestFullscreen","webkitEnterFullscreen"],i=[function(){return e.container},function(){return e.video}],r=!1;e:for(var n in i)for(var a in t)if(t[a]in i[n]()){(r={}).request=function(){return r.fullscreenableElement()[t[a]]()};var s=["exitFullscreen","webkitCancelFullScreen","mozCancelFullScreen","msExitFullscreen","webkitExitFullscreen"],o=["fullscreenElement","webkitFullscreenElement","mozFullScreenElement","msFullscreenElement","webkitFullscreenElement"];r.cancel=function(){return document[s[a]]()},r.element=function(){return document[o[a]]},r.event=["fullscreenchange","webkitfullscreenchange","mozfullscreenchange","MSFullscreenChange","webkitfullscreenchange"][a],r.fullscreenableElement=i[n];break e}if(!r){var l=function(e){switch(e.key){case"Escape":r.cancel()}};(r={event:"fakefullscreenchange",fullscreenableElement:function(){return e.container}}).request=function(){return r.element=function(){return e.container},MistUtil.event.send(r.event,null,document),document.addEventListener("keydown",l),!0},r.cancel=function(){return r.element=function(){return null},document.removeEventListener("keydown",l),MistUtil.event.send(r.event,null,document),!0},r.element=function(){return null}}var c=this.skin.icons.build("fullscreen");return MistUtil.event.addListener(c,"click",d),MistUtil.event.addListener(e.video,"dblclick",d),MistUtil.event.addListener(document,r.event,function(){r.element()==r.fullscreenableElement()?e.container.setAttribute("data-fullscreen",""):e.container.hasAttribute("data-fullscreen")&&e.container.removeAttribute("data-fullscreen"),e.player.resizeAll()},c),c}function d(){r.element()?r.cancel():r.request()}},"picture-in-picture":function(){if("setSize"in this.player&&this.info.hasVideo&&"audio"!=this.source.type.split("/")[1]&&document.pictureInPictureEnabled){var e=this;if("requestPictureInPicture"in e.video){var t=this.skin.icons.build("pip");return t.set=function(){document.pictureInPictureElement?MistUtil.class.remove(this,"off"):MistUtil.class.add(this,"off")},MistUtil.event.addListener(t,"click",function(){var i;(i=document.pictureInPictureElement?document.exitPictureInPicture():e.video.requestPictureInPicture())?i.then(function(){t.set()}):t.set()}),t.set(),t}}},tracks:function(){if(this.info&&this.video){var e=this,t=document.createElement("table");return i(this.info.meta.tracks),MistUtil.event.addListener(e.video,"metaUpdate_tracks",function(e){i(e.message.meta.tracks)},t),t}function i(i){MistUtil.empty(t),i=MistUtil.tracks.parse(i);var r={},n={};function a(t,i){if(i?e.log("User selected "+t+" track with id "+i):(e.log("User selected automatic track selection for "+t),MistUtil.event.send("trackSetToAuto",t,e.video)),e.options.setTracks||(e.options.setTracks={}),e.options.setTracks[t]=i,!0===i&&r[t]&&MistUtil.event.send("change",null,r[t]),"setTrack"in e.player.api)return e.player.api.setTrack(t,i);var n={};for(var a in r)"subtitle"!=a&&""!=r[a].value&&(n[a]=r[a].value);return""!=i&&(n[t]=i),"setTracks"in e.player.api?e.player.api.setTracks(n):"setSource"in e.player.api?e.player.api.setSource(MistUtil.http.url.addParam(e.source.url,n)):void 0}var s=MistUtil.object.keys(i,function(e,t){function i(e){switch(e){case"audio":return"aaaaaaa";case"video":return"aaaaaab";default:return e}}return i(e)>i(t)?1:i(e)1&&"player"in e&&"api"in e.player&&("setTrack"in e.player.api||"setTracks"in e.player.api||"setSource"in e.player.api)){var b=document.createElement("select");if(b.title="Select another "+l+" track",r[l]=b,b.trackType=l,f.appendChild(b),"subtitle"!=l){var k=document.createElement("option");b.appendChild(k),k.value="",k.appendChild(document.createTextNode("Automatic"))}var M=S(c[MistUtil.object.keys(c)[0]].same);if(M.length)(x=document.createElement("span")).className="mistvideo-description",f.appendChild(x),f.appendChild(document.createTextNode(M.join(" ")));function w(e){return""==e?-1:Number(e)}var U=MistUtil.object.keys(c,function(e,t){return w(e)-w(t)});for(var p in U){var C=c[U[p]];k=document.createElement("option");b.appendChild(k),k.value="idx"in C?C.idx:C.trackid,MistUtil.object.keys(C.different).length?k.appendChild(document.createTextNode(S(C.different).join(" "))):k.appendChild(document.createTextNode("Track "+(Number(p)+1)))}if(MistUtil.event.addListener(e.video,"playerUpdate_trackChanged",function(t){t.message.type==l&&"none"!=t.message.trackid&&(b.value=t.message.trackid,e.log("Player selected "+l+" track with id "+t.message.trackid))},b),"subtitle"==l){if(MistUtil.event.addListener(b,"change",function(){try{localStorage.mistSubtitleLanguage=c[this.value].lang}catch(e){}if("setWSSubtitle"in e.player.api)e.player.api.setWSSubtitle(""==this.value?void 0:this.value);else if(""!=this.value){var t=MistUtil.object.extend({},c[this.value]);t.label=S(t.describe).join(" "),t.src=MistUtil.http.url.addParam(u,{track:this.value}),e.player.api.setSubtitle(t)}else e.player.api.setSubtitle()}),"localStorage"in window&&null!=localStorage&&"mistSubtitleLanguage"in localStorage)for(var p in c)if(c[p].lang==localStorage.mistSubtitleLanguage){b.value=p;var T=document.createEvent("Event");T.initEvent("change"),b.dispatchEvent(T);break}}else MistUtil.event.addListener(b,"change",function(){this.trackType in n&&(n[this.trackType].checked=!0),a(this.trackType,this.value)})}else{var x;(x=document.createElement("span")).className="mistvideo-description",f.appendChild(x),x.appendChild(document.createTextNode(S(c[g[0]].same).join(" ")))}}function S(e){var t={trackid:0,language:1,width:2,bps:3,fpks:4,channels:5,codec:6,rate:7};return MistUtil.object.values(e,function(e,i,r,n){return t[e]>t[i]?1:t[e]=.999?n():t()},1e3))}()}function n(){e.container.removeAttribute("data-loading"),i&&e.timers.stop(i),i=!1}var a=["waiting","seeking","stalled"];for(var s in a)MistUtil.event.addListener(e.video,a[s],function(t){e.player.api&&!e.player.api.paused&&"container"in e&&r(t)},t);a=["seeked","playing","canplay","paused","ended"];for(var s in a)MistUtil.event.addListener(e.video,a[s],function(t){"container"in e&&n()},t);MistUtil.event.addListener(e.video,"progress",function(t){"container"in e&&"monitor"in e&&"vars"in e.monitor&&"score"in e.monitor.vars&&e.monitor.vars.score>.99&&n()},t)}return t},error:function(){var e=this,t=document.createElement("div");t.message=function(t,i,r){MistUtil.empty(this);var n=document.createElement("div");if(n.className="message",this.appendChild(n),!r.polling&&!r.passive&&!r.hideTitle){var a=document.createElement("h3");n.appendChild(a),a.appendChild(document.createTextNode("The "+(e.casting?"chromecast":"player")+" has encountered a problem"))}var s=document.createElement("p");if(n.appendChild(s),n.update=function(e){MistUtil.empty(s),s.innerHTML=e},t){e.info.on_error&&(t=e.info.on_error.replace(/\/,t)),n.update(t);var o=document.createElement("p");if(o.className="details mistvideo-description",n.appendChild(o),i)o.appendChild(document.createTextNode(i));else if("decodingIssues"in e.skin.blueprints){if("player"in e&&"api"in e.player&&e.video){if(i=[],void 0!==e.state&&i.push(["Stream state:",e.state]),void 0!==e.player.api.currentTime&&i.push(["Current video time:",MistUtil.format.time(e.player.api.currentTime)]),"video"in e&&"getVideoPlaybackQuality"in e.video){var l=e.video.getVideoPlaybackQuality();"droppedVideoFrames"in l&&"totalVideoFrames"in l&&l.totalVideoFrames&&i.push(["Frames dropped/total:",MistUtil.format.number(l.droppedVideoFrames)+"/"+MistUtil.format.number(l.totalVideoFrames)]),"corruptedVideoFrames"in l&&l.corruptedVideoFrames&&i.push(["Corrupted frames:",MistUtil.format.number(l.corruptedVideoFrames)])}i.push({0:["NETWORK EMPTY:","not yet initialized"],1:["NETWORK IDLE:","resource selected, but not in use"],2:["NETWORK LOADING:","data is being downloaded"],3:["NETWORK NO SOURCE:","could not locate source"]}[e.video.networkState]);if(i.push({0:["HAVE NOTHING:","no information about ready state"],1:["HAVE METADATA:","metadata has been loaded"],2:["HAVE CURRENT DATA:","data for the current playback position is available, but not for the next frame"],3:["HAVE FUTURE DATA:","data for current and next frame is available"],4:["HAVE ENOUGH DATA:","can start playing"]}[e.video.readyState]),!r.passive){var c=document.createElement("table");for(var d in i){var u=document.createElement("tr");for(var p in c.appendChild(u),i[d]){var h=document.createElement("td");u.appendChild(h),h.appendChild(document.createTextNode(i[d][p]))}}o.appendChild(c)}}var m,f=document.createElement("div");f.className="mistvideo-container mistvideo-column",f.style.textAlign="left",f.style.marginBottom="1em",n.appendChild(f),(m=e.UI.buildStructure({type:"forcePlayer"}))&&f.appendChild(m),(m=e.UI.buildStructure({type:"forceType"}))&&f.appendChild(m)}}return n};var i,r=!1,n=!1,a={};if(this.showError=function(s,o){o||(o={softReload:!!(e.player&&e.player.api&&e.player.api.load),reload:!0,nextCombo:!!e.info,polling:!1,passive:!1});var l=o.type?o.type:s;if(!(l in a)){if(!0===o.reload&&(e.options.reloadDelay&&!isNaN(Number(e.options.reloadDelay))?o.reload=Number(e.options.reloadDelay):o.reload=10),o.passive){if(!0===r)return;if(r)return i.update(s),void(n=(new Date).getTime());t.setAttribute("data-passive","")}else t.removeAttribute("data-passive");var c;r&&t.clear(),r=!o.passive||"passive",n=(new Date).getTime(),e.casting||(c=this.log(s,"error"));var d=t.message(s,!1,o);i=d;var u=document.createElement("div");if(u.className="mistvideo-buttoncontainer",d.appendChild(u),MistUtil.empty(u),e.casting&&!o.passive){var p={type:"button",label:"Stop casting",onclick:function(){e.detachFromCast()}};isNaN(o.softReload+"")||(p.delay=o.softReload),u.appendChild(e.UI.buildStructure(p))}if(o.softReload&&!e.casting){p={type:"button",label:"Reload video",onclick:function(){e.player.api.load()}};isNaN(o.softReload+"")||(p.delay=o.softReload),u.appendChild(e.UI.buildStructure(p))}if(o.reload){p={type:"button",label:"Reload player",onclick:function(){e.reload("Reloading because reload button was clicked.")}};isNaN(o.reload+"")||(p.delay=o.reload),u.appendChild(e.UI.buildStructure(p))}if(o.nextCombo){p={type:"button",label:"Next source",onclick:function(){e.nextCombo()}};isNaN(o.nextCombo+"")||(p.delay=o.nextCombo),u.appendChild(e.UI.buildStructure(p))}if(o.ignore){p={type:"button",label:"Ignore",onclick:function(){this.clearError(),a[l]=!0}};isNaN(o.ignore+"")||(p.delay=o.ignore),u.appendChild(e.UI.buildStructure(p))}o.polling&&u.appendChild(e.UI.buildStructure({type:"polling"})),MistUtil.class.add(t,"show"),"container"in e&&e.container.removeAttribute("data-loading"),c&&c.defaultPrevented&&(e.log("Error event was defaultPrevented, not showing."),t.clear())}},t.clear=function(){for(var i=t.querySelectorAll("svg.icon.timeout"),n=0;n=0;e--)r.removeChild(r.children[e])},e.setHtml=function(n){r.empty(),r.appendChild(n),"html"!=t&&(e.removeChild(i),e.appendChild(r),t="html")};var n=document.createElement("div");return e.triangle=n,n.className="triangle",e.appendChild(n),n.setMode=function(e,t){e||(e="bottom"),t||(t="left");var i=["bottom","top","right","left"];for(var r in i){this.style[i[r]]="";var n=MistUtil.format.ucFirst(i[r]);this.style["border"+n]="",this.style["border"+n+"Color"]=""}var a={top:"bottom",bottom:"top",left:"right",right:"left"};this.style[e]="-10px",this.style["border"+MistUtil.format.ucFirst(a[e])]="none",this.style["border"+MistUtil.format.ucFirst(e)+"Color"]="transparent",this.style[t]=0,this.style["border"+MistUtil.format.ucFirst(a[t])]="none"},e.setPos=function(e){var t={left:"auto",right:"auto",top:"auto",bottom:"auto"};for(var i in MistUtil.object.extend(t,e),t)isNaN(t[i])||(t[i]+="px"),this.style[i]=t[i]},e},button:function(e){var t=document.createElement("button"),i=this;if(e.onclick&&(MistUtil.event.addListener(t,"click",function(){e.onclick.call(i,arguments)}),e.delay)){var r=this.UI.buildStructure({type:"timeout",delay:e.delay,function:e.onclick});r&&t.appendChild(r)}return t.appendChild(document.createTextNode(e.label)),t},videobackground:function(e){e||(e={}),e.delay||(e.delay=5);for(var t=document.createElement("div"),i=this,r=[],n=0;n<2;n++){var a=document.createElement("canvas");a._context=a.getContext("2d"),t.appendChild(a),r.push(a)}var s=0,o=!1;return MistUtil.event.addListener(i.video,"playing",function(){o||(!function n(){if(e.alwaysDisplay||i.video.videoWidth/i.video.videoHeight!=t.clientWidth/t.clientHeight){r[s].removeAttribute("data-front"),++s>=r.length&&(s=0);var a=r[s],l=a._context;a.width=i.video.videoWidth,a.height=i.video.videoHeight,l.drawImage(i.video,0,0),a.setAttribute("data-front","")}i.player.api.paused?o=!1:i.timers.start(function(){n()},1e3*e.delay)}(),o=!0)}),t},subtitles:function(e){if(!("WebSocket"in window))return!1;var t=this;if(!("player"in t&&"api"in t.player&&"currentTime"in t.player.api))return!1;if(!("metaTrackSubscriptions"in t))return!1;var i=document.createElement("div"),r=document.createElement("span");i.appendChild(r);var n=document.createTextNode("");r.appendChild(n);var a=!1;function s(e){n.nodeValue=e.data.replace(/\<\/?[bui]\>/gi,"").replace(/{\/?[bui]}/gi,"").replace(/{\\a\d+}/gi,"").replace(/\<\/?font[^>]*?\>/gi,""),a&&(t.timers.stop(a),a=null),function i(r){a=t.timers.start(function(){if(t.player.api.paused)var r=MistUtil.event.addListener(t.video,"playing",function(){i(e.time+("duration"in e?e.duration:5e3)-1e3*t.player.api.currentTime),MistUtil.event.removeListener(r)});else n.nodeValue=""},r)}("duration"in e?e.duration:5e3)}if(MistUtil.event.addListener(t.video,"seeked",function(){n.nodeValue="",a&&t.timers.stop(a),a=null}),!("setWSSubtitle"in t.player.api)){var o=!1;t.player.api.setWSSubtitle=function(e){e!=o&&(void 0!==e&&t.metaTrackSubscriptions.add(e,s),e!=o&&t.metaTrackSubscriptions.remove(o,s),o="undefined"!=e&&e)}}return i},chromecast:function(){var e=this;if(!(!"".indexOf||e.options.host.indexOf("localhost")>-1||e.options.host.indexOf("::1")>-1)){var t,i,r,n,a=document.createElement("div"),s=document.createElement("div");s.className="mistvideo-casting";var o={},l={currentTime:!1,paused:!0,volume:1,muted:!1,buffer:[],loop:e.player.api?e.player.api.loop:e.options.loop};if(e.casting=!1,window.chrome&&window.chrome.cast||window.loadedCastApi)"loading"==window.loadedCastApi?(e.log("Not appending chromecast script - still loading"),e.timers.start(function(){d()},200)):(e.log("Not appending chromecast script - already loaded"),d());else{window.__onGCastApiAvailable=function(t,i){t||e.log("Error while loading chromecast API: "+i),d()},window.loadedCastApi="loading";var c=document.createElement("script");c.setAttribute("src","//www.gstatic.com/cv/js/sender/v1/cast_sender.js?loadCastFramework=1"),document.head.appendChild(c),e.log("Appending chromecast script")}return a}function d(c){if(!window.chrome||!window.chrome.cast||!window.chrome.cast.isAvailable||c>5)return a.parentNode&&a.parentNode.removeChild(a),e.log("Chromecast is not supported"),void console.warn(chrome,chrome.cast,chrome.cast?chrome.cast.isAvailable:void 0,cast);if(!window.cast)return c||(c=0),e.log("Casting api loaded but cast function not yet available, retrying.."),void e.timers.start(function(){d(c++)},200);e.log("Chromecast API loaded"),window.loadedCastApi&&"loading"!=window.loadedCastApi||(window.loadedCastApi=!0);var u=document.createElement("google-cast-launcher");function p(a){MistUtil.class.remove(u,"active"),MistUtil.class.remove(e.container,"casting"),s.parentNode&&s.parentNode.removeChild(s),!a&&cast.framework.CastContext.getInstance().getCurrentSession()&&cast.framework.CastContext.getInstance().getCurrentSession().endSession(!0),t?(e.player.api=t,t.currentTime=l.currentTime,t.play(),e.reload=i,e.nextCombo=r,e.unload=n):e.player.api.play(),e.player.api&&e.player.api.setTracks&&MistUtil.object.keys(o).length&&e.player.api.setTracks(o),e.casting=!1,e.log("Detached chromecast session")}a.appendChild(u),cast.framework.CastContext.getInstance().setOptions({receiverApplicationId:"E5F1558C",autoJoinPolicy:chrome.cast.AutoJoinPolicy.ORIGIN_SCOPED}),e.detachFromCast=p,u.addEventListener("click",function(a){if(a.stopPropagation(),MistUtil.class.has(u,"active"))p();else{function c(){cast.framework.CastContext.getInstance().getCurrentSession().addMessageListener("urn:x-cast:mistcaster",function(t,i){if(e.destroyed&&p(),(i=JSON.parse(i)).type)switch(i.type){case"log":case"error":e.log("[Chromecast] "+i.message,i.type);break;case"showError":e.showError.apply(e,i.args);break;case"event":switch(i.event){case"timeupdate":l.currentTime=i.currentTime,MistUtil.event.send(i.event,"chromecast",e.video);break;case"progress":l.buffer=i.buffer,MistUtil.event.send(i.event,"chromecast",e.video);break;case"pause":case"paused":case"ended":case"play":case"playing":l.paused=i.paused,MistUtil.event.send(i.event,"chromecast",e.video);break;case"volumechange":l.volume=i.volume,l.muted=i.muted,MistUtil.event.send(i.event,"chromecast",e.video);break;default:MistUtil.event.send(i.event,"chromecast",e.video)}break;case"detach":i.n==e.n&&p(!0);break;default:console.log("Unknown chromecast message type",i)}});var a={type:"load",n:e.n,options:{host:e.options.host,loop:e.options.loop,poster:e.options.poster,streaminfo:e.options.streaminfo,urlappend:e.options.urlappend,forcePriority:e.options.forcePriority,setTracks:e.options.setTracks,controls:!1,skin:"default"},stream:e.stream};e.info&&"live"!=e.info.type&&(a.time=e.player.api.currentTime),"dev"==e.options.skin&&(a.options.skin=e.options.skin),e.player&&e.player.api&&(a.volume=e.player.api.volume,a.muted=e.player.api.muted,a.options.loop=e.player.api.loop),MistCast.send(a),t=e.player.api,i=e.reload,r=e.nextCombo,n=e.unload,o=e.options.setTracks?e.options.setTracks:{},e.player.api=new Proxy(t,{get:function(e,t,i){var r=e[t];switch(t){case"muted":case"volume":case"currentTime":case"paused":case"loop":return l[t];case"buffered":return new function(){this.length=l.buffer.length,this.start=function(e){return l.buffer[e][0]},this.end=function(e){return l.buffer[e][1]}};case"setTracks":case"play":case"pause":return function(){for(var e=[],i=0;i=t.scrollHeight-5}),r.logs)o(r.logs[l].time,r.logs[l].message,r.logs[l].data);return MistUtil.event.addListener(r.options.target,"log",function(e){if(e.message){var t={};r.player&&r.player.api&&"currentTime"in r.player.api&&(t.currentTime=r.player.api.currentTime),o(new Date,e.message,t)}},e),MistUtil.event.addListener(r.options.target,"error",function(e){if(e.message){var t={type:"error"};r.player&&r.player.api&&"currentTime"in r.player.api&&(t.currentTime=r.player.api.currentTime),o(new Date,e.message,t)}},e),e},decodingIssues:function(){if(this.player){var e=this,t=document.createElement("div");if(e.player.api){var i={"Playback score":function(){if("monitor"in e){if("vars"in e.monitor&&"score"in e.monitor.vars&&e.monitor.vars.values.length){var t=e.monitor.vars.values[e.monitor.vars.values.length-1];if("score"in t){Math.min(1,Math.max(0,t.score));return{x:t.clock,y:Math.min(1,Math.max(0,t.score)),options:{y:{min:0,max:1},x:{count:10}},val:Math.round(100*Math.min(1,Math.max(0,e.monitor.vars.score)))+"%"}}}return 0}},"Corrupted frames":function(){if(e.player.api&&"getVideoPlaybackQuality"in e.player.api){var t=e.player.api.getVideoPlaybackQuality();if(t)return t.corruptedVideoFrames?{val:MistUtil.format.number(t.corruptedVideoFrames),x:.001*(new Date).getTime(),y:t.corruptedVideoFrames,options:{x:{count:10}}}:0}},"Dropped frames":function(){if(e.player.api){if("getVideoPlaybackQuality"in e.player.api){var t=e.player.api.getVideoPlaybackQuality();if(t)return t.droppedVideoFrames?MistUtil.format.number(t.droppedVideoFrames):0}if("webkitDroppedFrameCount"in e.player.api)return e.player.api.webkitDroppedFrameCount}},"Total frames":function(){if(e.player.api&&"getVideoPlaybackQuality"in e.player.api){var t=e.player.api.getVideoPlaybackQuality();if(t)return MistUtil.format.number(t.totalVideoFrames)}},"Decoded audio":function(){if(e.player.api)return MistUtil.format.bytes(e.player.api.webkitAudioDecodedByteCount)},"Decoded video":function(){if(e.player.api)return MistUtil.format.bytes(e.player.api.webkitVideoDecodedByteCount)},"Negative acknowledgements":function(){if(e.player.api)return MistUtil.format.number(e.player.api.nackCount)},"Picture losses":function(){return MistUtil.format.number(e.player.api.pliCount)},"Packets lost":function(){return MistUtil.format.number(e.player.api.packetsLost)},"Packets received":function(){return MistUtil.format.number(e.player.api.packetsReceived)},"Bytes received":function(){if(e.player.api)return MistUtil.format.bytes(e.player.api.bytesReceived)},"Local latency [ms]":function(){if(e.player.api&&"getLatency"in e.player.api){var t=e.player.api.getLatency();return t?new Promise(function(e,i){t.then(function(t){var i=[];for(var r in t)t[r]&&i.push(r[0]+":"+Math.round(1e3*t[r]));i.length?e(i.join(" ")):e()},i)}):new Promise(function(e,t){e()},function(){})}},"Current bitrate":function(){var t;return e.player.monitor&&"currentBps"in e.player.monitor?(t=MistUtil.format.bits(e.player.monitor.currentBps))?t+"ps":t:e.player.api&&"currentBps"in e.player.api?(t=MistUtil.format.bits(e.player.api.currentBps()))?t+"ps":t:void 0},"Framerate in":function(){if(e.player.api&&"framerate_in"in e.player.api)return MistUtil.format.number(e.player.api.framerate_in())},"Framerate out":function(){if(e.player.api&&"framerate_out"in e.player.api)return MistUtil.format.number(e.player.api.framerate_out())}},r=[];for(var n in i)void 0!==i[n]()&&a({name:n,function:i[n]});t.update=function(){for(var i in r)r[i]();e.timers.start(function(){t.update()},1e3)},t.update()}return t}function a(e){var i=document.createElement("label");t.appendChild(i),i.style.display="none";var n=document.createElement("span");i.appendChild(n),n.appendChild(document.createTextNode(e.name+":")),n.className="mistvideo-description";var a=document.createElement("span");i.appendChild(a);var s=document.createTextNode(e.value?e.value:"");a.appendChild(s);var o=document.createElement("span");a.appendChild(o),i.set=function(e){if(0!==e&&(this.style.display=""),"object"==typeof e){try{if(e instanceof Promise)return void e.then(function(e){i.set(e)},function(){})}catch(e){}if("val"in e&&(s.nodeValue=e.val,a.className="value"),o.children.length)return(t=o.children[0]).addData(e);var t=MistUtil.createGraph({x:[e.x],y:[e.y]},e.options);return o.style.display="",MistUtil.empty(o),o.appendChild(t)}return s.nodeValue=e},t.appendChild(i),r.push(function(){var t=e.function();i.set(t)})}},forcePlayer:function(){var e=document.createElement("label");e.title="Reload MistVideo and use the selected player";var t=this,i=document.createElement("span");e.appendChild(i),i.appendChild(document.createTextNode("Force player: "));var r=document.createElement("select");e.appendChild(r);var n=document.createElement("option");for(var a in r.appendChild(n),n.value="",n.appendChild(document.createTextNode("Automatic")),mistplayers){n=document.createElement("option");r.appendChild(n),n.value=a,n.appendChild(document.createTextNode(mistplayers[a].name))}return this.options.forcePlayer&&(r.value=this.options.forcePlayer),MistUtil.event.addListener(r,"change",function(){t.options.forcePlayer=""!=this.value&&this.value,t.options.forcePlayer!=t.playerName&&t.reload("Reloading to force player.")}),e},forceType:function(){if(this.info){var e=document.createElement("label");e.title="Reload MistVideo and use the selected protocol";var t=this,i=document.createElement("span");e.appendChild(i),i.appendChild(document.createTextNode("Force protocol: "));var r=document.createElement("select");e.appendChild(r);var n=document.createElement("option");r.appendChild(n),n.value="",n.appendChild(document.createTextNode("Automatic"));var a={};for(var s in t.info.source){var o=t.info.source[s];if(!(o.type in a)){a[o.type]=1;n=document.createElement("option");r.appendChild(n),n.value=o.type,n.appendChild(document.createTextNode(MistUtil.format.mime2human(o.type)))}}return this.options.forceType&&(r.value=this.options.forceType),MistUtil.event.addListener(r,"change",function(){t.options.forceType=""!=this.value&&this.value,t.source&&t.options.forceType==t.source.type||t.reload("Reloading to force new type.")}),e}},forceSource:function(){var e=document.createElement("label");e.title="Reload MistVideo and use the selected source";var t=this,i=document.createElement("span");e.appendChild(i),i.appendChild(document.createTextNode("Force source: "));var r=document.createElement("select");e.appendChild(r);var n=document.createElement("option");for(var a in r.appendChild(n),n.value="",n.appendChild(document.createTextNode("Automatic")),t.info.source){var s=t.info.source[a];n=document.createElement("option");r.appendChild(n),n.value=a,n.appendChild(document.createTextNode(s.url+" ("+MistUtil.format.mime2human(s.type)+")"))}return this.options.forceSource&&(r.value=this.options.forceSource),MistUtil.event.addListener(r,"change",function(){t.options.forceSource=""!=this.value&&this.value,t.options.forceSource!=t.source.index&&t.reload("Reloading to force new source.")}),e}}},MistSkins.dev.css={skin:misthost+"/skins/dev.css"},MistSkins.dev.structure.submenu=MistUtil.object.extend({},MistSkins.default.structure.submenu,!0),MistSkins.dev.structure.submenu.type="draggable",MistSkins.dev.structure.submenu.style.width="25em",MistSkins.dev.structure.submenu.children.unshift({type:"container",style:{flexShrink:1},classes:["mistvideo-column"],children:[{if:function(){return this.playerName&&this.source},then:{type:"container",classes:["mistvideo-description","mistvideo-displayCombo"],style:{display:"block"},children:[{type:"playername",style:{display:"inline"}},{type:"text",text:"is playing",style:{margin:"0 0.2em"}},{type:"mimetype"}]}},{type:"log"},{type:"decodingIssues"},{type:"container",classes:["mistvideo-column","mistvideo-devcontrols"],style:{"font-size":"0.9em"},children:[{type:"text",text:"Player control"},{type:"container",classes:["mistvideo-devbuttons"],style:{"flex-wrap":"wrap"},children:[{type:"button",title:"Build MistVideo again",label:"MistVideo.reload();",onclick:function(){this.reload("Dev-reload button clicked.")}},{type:"button",title:"Switch to the next available player and source combination",label:"MistVideo.nextCombo();",onclick:function(){this.nextCombo()}}]},{type:"forcePlayer"},{type:"forceType"}]}]});var mistplayers={};function MistPlayer(){}function mistPlay(e,t){return new MistVideo(e,t)}function MistVideo(e,t){var i=this;function r(e){if("meta"in e&&"tracks"in e.meta){var t=e.meta.tracks;for(var i in t)if("video"==t[i].type)return!0}return!1}function n(e){if(i.player&&i.player.api&&i.player.api.unload&&(i.log("Received new stream info while a player was already loaded: unloading player"),i.player.api.unload()),i.info=e,i.info.updated=new Date,MistUtil.event.send("haveStreamInfo",e,i.options.target),i.log("Stream info was loaded succesfully."),"error"in e){var n=e.error;return"on_error"in e?(i.log(n),n=e.on_error):"perc"in e&&(n+=" ("+Math.round(10*e.perc)/10+"%)"),void i.showError(n,{reload:!0,hideTitle:!0})}if(i.calcSize=function(e){e||(e={width:!1,height:!1});var r=e.width||!!("width"in t&&t.width)&&t.width,n=e.height||!!("height"in t&&t.height)&&t.height;if(this.info&&"source"in this.info)if(this.info.hasVideo&&"audio"!=this.source.type.split("/")[1]){if(!r||!n){var a=i.info.width/i.info.height;if(r||n)r?n=r/a:r=n*a;else{var s="maxwidth"in t&&t.maxwidth?t.maxwidth:window.innerWidth,o="maxheight"in t&&t.maxheight?t.maxheight:window.innerHeight;r=i.info.width,n=i.info.height;function l(e){r/=e,n/=e}r<426&&l(r/426),n<240&&l(n/240),s&&r>s&&l(r/s),o&&n>o&&l(n/o)}}}else r||(r=480),n||(n=42);else r=640,n=480;return this.size={width:Math.round(r),height:Math.round(n)},this.size},e.hasVideo=r(e),"live"==e.type){var a=0;for(var s in i.info.meta.tracks)a=Math.max(a,i.info.meta.tracks[s].lastms);e.lastms=a}else{var o=i.resumeTime;if(o){var l=function(){i.player&&i.player.api&&(i.player.api.currentTime=o),this.removeEventListener("initialized",l)};MistUtil.event.addListener(i.options.target,"initialized",l)}}i.options.ABR_bitrate&&i.options.ABR_resize&&i.info&&!i.info.selver&&(i.options.ABR_bitrate=!1),i.choosePlayer()?(i.reporting&&i.reporting.report({player:i.playerName,sourceType:i.source.type,sourceUrl:i.source.url,pageUrl:location.href}),i.player=new mistplayers[i.playerName].player,i.player.onreadylist=[],i.player.onready=function(e){this.onreadylist.push(e)},i.player.build(i,function(e){if(i.log("Building new player"),i.container.removeAttribute("data-loading"),i.video=e,i.reporting&&i.reporting.init(),"api"in i.player){i.monitor={MistVideo:i,delay:1,averagingSteps:20,threshold:function(){return"webrtc"==this.MistVideo.source.type?.95:.75},init:function(){if(!this.vars||!this.vars.active){this.MistVideo.log("Enabling monitor"),this.vars={values:[],score:!1,active:!0};var e=this;!function t(){e.vars&&e.vars.active&&(e.vars.timer=e.MistVideo.timers.start(function(){var i=e.calcScore();!1!==i&&e.check(i)&&e.action(),t()},1e3*e.delay))}()}},destroy:function(){this.vars&&this.vars.active&&(this.MistVideo.log("Disabling monitor"),this.MistVideo.timers.stop(this.vars.timer),delete this.vars)},reset:function(){this.vars&&this.vars.active?(this.MistVideo.log("Resetting monitor"),this.vars.values=[]):this.init()},calcScore:function(){var e=this.vars.values;if(e.push(this.getValue()),e.length<=1)return!1;var t=this.valueToScore(e[0],e[e.length-1]);return e.length>this.averagingSteps&&e.shift(),t=Math.max(t,e[e.length-1].score),this.vars.score=t,i.reporting&&i.reporting.stats.set("playbackScore",Math.round(10*t)/10),t},valueToScore:function(e,t){var i=1;return"player"in this.MistVideo&&"api"in this.MistVideo.player&&"playbackRate"in this.MistVideo.player.api&&(i=this.MistVideo.player.api.playbackRate),(t.video-e.video)/(t.clock-e.clock)/i},getValue:function(){var e={clock:.001*(new Date).getTime(),video:this.MistVideo.player.api.currentTime};return this.vars.values.length&&(e.score=this.valueToScore(this.vars.values[this.vars.values.length-1],e)),e},check:function(e){return!(this.vars.values.length<.5*this.averagingSteps)&&(e=e.socket.CLOSING&&e.init(),this.send_queue.push(t)};var t=!1;if(e.socket.setTracks=function(){e.s({type:"tracks",meta:MistUtil.object.keys(e.subscriptions).join(",")})},e.socket.onopen=function(){for(i.log("Metadata socket opened"),e.socket.setTracks(),1!=i.player.api.playbackRate&&e.s({type:"set_speed",play_rate:i.player.api.playbackRate}),e.s({type:"seek",seek_time:Math.round(1e3*i.player.api.currentTime),ff_to:Math.round(1e3*(i.player.api.currentTime+5))}),e.socket.addEventListener("message",function(r){if(r.data){var n=JSON.parse(r.data);if(n){if("time"in n&&"track"in n&&"data"in n){var a=!1;if("all"in e.subscriptions&&(e.subscriptions.all.buffer.push(n),a=!0),n.track in e.subscriptions&&(e.subscriptions[n.track].buffer.push(n),a=!0),a)if(e.checktimer){var s=i.timers.list[e.checktimer];if(s)s>(new Date).getTime()+n.time-1e3*i.player.api.currentTime&&(i.log("The metadata socket received a message that should be displayed sooner than the current check time; resetting"),i.timers.stop(e.checktimer),e.checktimer=null,e.check())}else e.check()}if("type"in n)switch(n.type){case"on_time":!t&&n.data.current>1e3*(i.player.api.currentTime+30)&&(t=!0,e.s({type:"hold"}),i.log("Pausing metadata buffer because it is very far ahead, checking again in 5 seconds: "+n.data.current+" > "+1e3*i.player.api.currentTime),i.timers.start(function(){i.player.api.paused||e.s({type:"play"}),e.s({type:"fast_forward",ff_to:Math.round(1e3*(i.player.api.currentTime+5))})},5e3));break;case"seek":for(var o in e.subscriptions)e.subscriptions[o].buffer=[];i.log("Cleared metadata buffer after completed seek"),e.checktimer&&(i.timers.stop(e.checktimer),e.checktimer=null)}}else i.log("Subtitle websocket received invalid message.")}else i.log("Subtitle websocket received empty message.")}),e.socket.onclose=function(){i.log("Metadata socket closed")};e.send_queue.length&&e.socket.readyState==e.socket.OPEN;)e.s(e.send_queue.shift())},!("seeked"in this.listeners)){var r=(new Date).getTime();e.check=function(){if(e.checktimer&&(i.timers.stop(e.checktimer),e.checktimer=null),!i.player.api.paused){var t=null;for(var n in e.subscriptions){for(var a=e.subscriptions[n].buffer;a.length&&a[0].time<=1e3*i.player.api.currentTime;){var s=a.shift();if(!(s.time<1e3*(i.player.api.currentTime-5)))for(var o in e.subscriptions[n].callbacks)e.subscriptions[n].callbacks[o].call(i,s)}a.length&&(t=Math.min(null===t?1e9:t,a[0].time))}var l=(new Date).getTime();if(l>r+5e3&&(e.s({type:"fast_forward",ff_to:Math.round(1e3*(i.player.api.currentTime+5))}),r=l),t){var c=t-1e3*i.player.api.currentTime;e.checktimer=i.timers.start(function(){e.check()},c)}}},this.listeners.seeked=MistUtil.event.addListener(i.video,"seeked",function(){for(var t in e.subscriptions)e.subscriptions[t].buffer=[];e.s({type:"seek",seek_time:Math.round(1e3*i.player.api.currentTime),ff_to:Math.round(1e3*(i.player.api.currentTime+5))}),r=(new Date).getTime()}),this.listeners.pause=MistUtil.event.addListener(i.video,"pause",function(){e.s({type:"hold"}),i.timers.stop(e.checktimer),e.checktimer=null}),this.listeners.playing=MistUtil.event.addListener(i.video,"playing",function(){e.s({type:"play"}),e.checktimer||e.check()}),this.listeners.ratechange=MistUtil.event.addListener(i.video,"ratechange",function(){e.s({type:"set_speed",play_rate:i.player.api.playbackRate})})}e.socket.readyState==e.socket.OPEN&&e.socket.onopen()},destroy:function(){for(var e in i.log("Closing metadata socket.."),this.socket.close(),this.socket=null,this.subscriptions={},this.listeners)MistUtil.event.removeListener(this.listeners[e]);this.listeners={}},add:function(e,t){"function"!=typeof e||t||(t=e,e="all"),"function"==typeof t&&(e in this.subscriptions||(this.subscriptions[e]={buffer:[],callbacks:[]}),this.subscriptions[e].callbacks.push(t),null===this.socket?this.init():this.socket.setTracks())},remove:function(e,t){if(e in this.subscriptions){for(var i in this.subscriptions[e].callbacks)if(t==this.subscriptions[e].callbacks[i]){this.subscriptions[e].callbacks.splice(i,1);break}0==this.subscriptions[e].callbacks.length&&(delete this.subscriptions[e],MistUtil.object.keys(this.subscriptions).length?this.socket.setTracks():this.destroy())}}},"function"==typeof t.subscribeToMetaTrack&&(t.subscribeToMetaTrack=[["all",t.subscribeToMetaTrack]]),t.subscribeToMetaTrack.length))for(var n in"object"!=typeof t.subscribeToMetaTrack[0]&&(t.subscribeToMetaTrack=[t.subscribeToMetaTrack]),t.subscribeToMetaTrack)i.metaTrackSubscriptions.add.apply(i.metaTrackSubscriptions,t.subscribeToMetaTrack[n])}}MistUtil.empty(i.options.target),new MistSkin(i),i.container=new MistUI(i),i.options.target.appendChild(i.container),i.container.setAttribute("data-loading",""),i.video.p=i.player;r=["abort","canplay","canplaythrough",,"emptied","ended","loadeddata","loadedmetadata","loadstart","pause","play","playing","ratechange","seeked","seeking","stalled","volumechange","waiting","metaUpdate_tracks","resizing"];for(var n in r)MistUtil.event.addListener(i.video,r[n],function(e){e.message&&"chromecast"==e.message||i.log("Player event fired: "+e.type)});if(MistUtil.event.addListener(i.video,"error",function(e){var t;if("player"in i&&"api"in i.player&&"error"in i.player.api&&i.player.api.error)if("message"in i.player.api.error)t=i.player.api.error.message;else if("code"in i.player.api.error&&i.player.api.error instanceof MediaError){var r={1:"MEDIA_ERR_ABORTED: The fetching of the associated resource was aborted by the user's request.",2:"MEDIA_ERR_NETWORK: Some kind of network error occurred which prevented the media from being successfully fetched, despite having previously been available.",3:"MEDIA_ERR_DECODE: Despite having previously been determined to be usable, an error occurred while trying to decode the media resource, resulting in an error.",4:"MEDIA_ERR_SRC_NOT_SUPPORTED: The associated resource or media provider object (such as a MediaStream) has been found to be unsuitable."};t=i.player.api.error.code in r?r[i.player.api.error.code]:"MediaError code "+i.player.api.error.code}else"string"!=typeof(t=i.player.api.error)&&(t=JSON.stringify(t));else t="An error was encountered.";"Stream is online"==i.state?i.showError(t):(i.log(t,"error"),i.showError(i.state,{polling:!0}))}),"setSize"in i.player&&(i.player.videocontainer=i.video.parentNode,i.video.currentTarget=i.options.target,MistUtil.class.has(i.options.target,"mistvideo-secondaryVideo")||(i.player.resizeAll=function(){function e(t,i){if(t.video.currentTarget==i)return t.video;if(t.secondary)for(var r=0;r3600&&(t.reloadDelay/=1e3,this.log("A reloadDelay of more than an hour was set: assuming milliseconds were intended. ReloadDelay is now "+t.reloadDelay+"s")),new MistSkin(this),this.checkCombo=function(e,t){e||(e={}),e=MistUtil.object.extend(MistUtil.object.extend({},this.options),e);var r,n,a=!1;for(var s in e.forceSource?(r=[i.info.source[e.forceSource]],i.log("Forcing source "+e.forceSource+": "+r[0].type+" @ "+r[0].url)):e.forceType?(r=i.info.source.filter(function(t){return t.type==e.forceType}),i.log("Forcing type "+e.forceType)):r=i.info.source,mistplayers)mistplayers[s].shortname=s;e.forcePlayer&&mistplayers[e.forcePlayer]?(n=[mistplayers[e.forcePlayer]],i.log("Forcing player "+e.forcePlayer)):n=MistUtil.object.values(mistplayers),r=[].concat(r);var o={first:"source",source:[function(e){return"origIndex"in e?e.origIndex:(e.origIndex=i.info.source.indexOf(e),e.origIndex)}],player:[{priority:1}]},l={inner:"player",outer:"source"};if(e.forcePriority){if("source"in e.forcePriority){if(!(e.forcePriority.source instanceof Array))throw"forcePriority.source is not an array.";o.source=e.forcePriority.source.concat(o.source),MistUtil.array.multiSort(r,o.source)}if("player"in e.forcePriority){if(!(e.forcePriority.player instanceof Array))throw"forcePriority.player is not an array.";o.player=e.forcePriority.player.concat(o.player),MistUtil.array.multiSort(n,o.player)}"first"in e.forcePriority&&(o.first=e.forcePriority.first),"player"==o.first&&(l.outer="player",l.inner="source")}var c={player:{list:n,current:!1},source:{list:r,current:!1}};if(e.startCombo){e.startCombo.started={player:!1,source:!1};for(s=0;s=2))for(var v in c[l.inner].list)if(c[l.inner].current=v,!(d(l.inner)>=1)){a=c.source.list[c.source.current];var y=c.player.list[c.player.current].shortname,g=mistplayers[y];if(g.isMimeSupported(a.type)){var b=g.isBrowserSupported(a.type,a,i);if(b){var k=p(b);if(k>u.score&&(t||i.log("Found a "+(u.score?"better":"working")+" combo: "+g.name+" with "+a.url+" (Score: "+k+")"),(u={score:k,player:y,source:a,source_index:c.source.current}).score==m))return u}}}return!!u.score&&u},this.choosePlayer=function(){i.log("Checking available players..");var e=this.checkCombo();if(!e)return!1;var t=mistplayers[e.player],r=e.source;return i.log("Selected: "+t.name+" with "+r.type+" @ "+r.url),i.playerName=e.player,(r=MistUtil.object.extend({},r)).index=e.source_index,r.url=i.urlappend(r.url),i.source=r,MistUtil.event.send("comboChosen","Player/source combination selected",i.options.target),!0},i.calcSize=function(){return{width:640,height:480}},MistUtil.empty(i.options.target),new MistSkin(i),i.container=new MistUI(i,i.skin.structure.placeholder),i.options.target.appendChild(i.container),i.container.setAttribute("data-loading",""),"WebSocket"in window){!function e(){i.log("Opening stream status stream through websocket..");var t,s=i.options.host.replace(/^http/i,"ws");s=MistUtil.http.url.addParam(i.urlappend(s+"/json_"+encodeURIComponent(i.stream)+".js"),{metaeverywhere:1,inclzero:1});try{t=new WebSocket(s)}catch(e){return i.log("Error while attempting to open WebSocket to "+s),void a()}i.socket=t,t.die=!1,t.destroy=function(){this.die=!0,i.reporting&&(i.reporting.reportStats(),i.reporting=!1),this.onclose=function(){},this.close()},t.timeOut=i.timers.start(function(){t.readyState<=1&&(t.destroy(),a())},5e3),t.onopen=function(e){this.wasConnected=!0,i.reporting||(i.reporting={stats:{set:function(e,t){this.d[e]=t},add:function(e,t){void 0===t&&(t=1),this.d[e]+=t},d:{nWaiting:0,timeWaiting:0,nStalled:0,timeStalled:0,timeUnpaused:0,nError:0,nLog:0,videoHeight:null,videoWidth:null,playerHeight:null,playerWidth:null},last:{firstPlayback:null,nWaiting:0,timeWaiting:0,nStalled:0,timeStalled:0,timeUnpaused:0,nError:0,lastError:null,playbackScore:1,nLog:0,autoplay:null,videoHeight:null,videoWidth:null,playerHeight:null,playerWidth:null}},report:function(e){1==i.socket.readyState&&i.socket.send(JSON.stringify(e))},reportStats:function(){var e={},t=!1,r=i.logs.slice(this.stats.last.nLog);for(var n in this.stats.d)this.stats.d[n]!=this.stats.last[n]&&(e[n]=this.stats.d[n],this.stats.last[n]=e[n],t=!0);if(t){if(r.length)for(var n in e.logs=[],r)e.logs.push(r[n].message);this.report(e)}i.timers.start(function(){i.reporting&&i.reporting.reportStats()},5e3)},init:function(){var e=i.video,t=MistUtil.event.addListener(e,"playing",function(){i.reporting.stats.set("firstPlayback",(new Date).getTime()-i.bootMs),MistUtil.event.removeListener(t)});if(MistUtil.event.addListener(e,"waiting",function(){i&&i.reporting&&i.reporting.stats.add("nWaiting")}),MistUtil.event.addListener(e,"stalled",function(){i&&i.reporting&&i.reporting.stats.add("nStalled")}),MistUtil.event.addListener(i.options.target,"error",function(e){i&&i.reporting&&(i.reporting.stats.add("nError"),i.reporting.stats.set("lastError",e.message))},e),Object&&Object.defineProperty){var r=0,n=!1,a=0,s=!1,o=0,l=!1,c=i.reporting.stats.d;Object.defineProperty(c,"timeWaiting",{get:function(){return r+(n?(new Date).getTime()-n:0)}}),Object.defineProperty(c,"timeStalled",{get:function(){return a+(s?(new Date).getTime()-s:0)}}),Object.defineProperty(c,"timeUnpaused",{get:function(){return o+(l?(new Date).getTime()-l:0)}}),Object.defineProperty(c,"nLog",{get:function(){return i.logs.length}}),Object.defineProperty(c,"videoHeight",{get:function(){return i.video?i.video.videoHeight:null}}),Object.defineProperty(c,"videoWidth",{get:function(){return i.video?i.video.videoWidth:null}}),Object.defineProperty(c,"playerHeight",{get:function(){return i.video?i.video.clientHeight:null}}),Object.defineProperty(c,"playerWidth",{get:function(){return i.video?i.video.clientWidth:null}}),MistUtil.event.addListener(e,"waiting",function(){r=c.timeWaiting,n=(new Date).getTime()}),MistUtil.event.addListener(e,"stalled",function(){a=c.timeStalled,s=(new Date).getTime()});var d=["playing","pause"];for(var u in d)MistUtil.event.addListener(e,d[u],function(){r=c.timeWaiting,a=c.timeStalled,n=!1,s=!1});MistUtil.event.addListener(e,"playing",function(){o=c.timeUnpaused,l=(new Date).getTime()}),MistUtil.event.addListener(e,"pause",function(){o=c.timeUnpaused,l=!1})}this.reportStats()}})},t.onclose=function(t){if(!this.die)return this.wasConnected?(i.log("Reopening websocket.."),void e()):void a()};var o=!1;t.addEventListener("message",function(e){t.timeOut&&(i.timers.stop(t.timeOut),t.timeOut=!1);var a=JSON.parse(e.data);if(a||i.showError("Error while parsing stream status stream. Obtained: "+e.data.toString(),{reload:!0}),"error"in a){var s;switch(e=a.error,"on_error"in a?(i.log(e),e=a.on_error):"perc"in a&&(e+=" ("+Math.round(10*a.perc)/10+"%)"),i.state=a.error,a.error){case"Stream is offline":i.info=!1,i.player&&i.player.api&&i.player.api.currentTime&&(i.resumeTime=i.player.api.currentTime);case"Stream is initializing":case"Stream is booting":case"Stream is waiting for data":case"Stream is shutting down":case"Stream status is invalid?!":if(i.player&&i.player.api&&!i.player.api.paused)return i.log(a.error,"error"),o||(o=MistUtil.event.addListener(i.video,"ended",function(){i.showError(a.error,{polling:!0})})),void(o=MistUtil.event.addListener(i.video,"waiting",function(){i.showError(a.error,{polling:!0})}));s={polling:!0};break;default:s={reload:!0}}i.showError(e,s)}else{if(i.state="Stream is online",i.clearError(),o&&MistUtil.event.removeListener(o),!i.info)return void n(a);var l=function e(t,i){if(t==i)return!1;if("object"==typeof t&&void 0!==i){var r={};for(var n in t)if(!(MistUtil.array.indexOf(["lastms","hasVideo"],n)>=0)){var a=e(t[n],i[n]);a&&(r[n]=!0===a?[t[n],i[n]]:a)}for(var n in i)MistUtil.array.indexOf(["lastms","hasVideo"],n)>=0||n in t||(r[n]=[t[n],i[n]]);return!!MistUtil.object.keys(r).length&&r}return!0}(a,i.info);if(l){if("source"in l&&"error"in i.info)return void i.reload("Reloading, stream info has error");i.info=MistUtil.object.extend(i.info,a),i.info.updated=new Date;var c=!1;for(var d in l)switch(d){case"meta":for(var u in l[d])switch(u){case"tracks":i.info.hasVideo=r(i.info),MistUtil.event.send("metaUpdate_tracks",a,i.video)}break;case"width":case"height":c=!0}c&&i.player.resize()}else i.log("Metachange: no differences detected")}})}()}else a();return this.unload=function(e){if(!this.destroyed){for(var t in this.log("Unloading.."),this.destroyed=!0,this.timers.stop("all"),this.errorListeners){var r=this.errorListeners[t];if(r.src in MistUtil.scripts.list){var n=MistUtil.array.indexOf(MistUtil.scripts.list[r.src].subscribers);n>=0&&MistUtil.scripts.list[r.src].subscribers.splice(n,1)}}if("monitor"in i&&"destroy"in i.monitor&&i.monitor.destroy(),this.socket&&(this.reporting&&(this.reporting.reportStats(),this.reporting.report({unload:e||null})),this.socket.destroy()),this.player&&this.player.api&&("pause"in this.player.api&&this.player.api.pause(),"setSource"in this.player.api&&this.player.api.setSource(""),"unload"in this.player.api))try{this.player.api.unload()}catch(a){i.log("Error while unloading player: "+a.message)}if(this.metaTrackSubscriptions&&this.metaTrackSubscriptions.socket&&this.metaTrackSubscriptions.destroy(),this.UI&&this.UI.elements)for(var t in this.UI.elements){var a=this.UI.elements[t];if("attachedListeners"in a)for(var t in a.attachedListeners)MistUtil.event.removeListener(a.attachedListeners[t]);a.parentNode&&a.parentNode.removeChild(a)}this.video&&MistUtil.empty(this.video),"container"in this&&(MistUtil.empty(this.container),delete this.container),MistUtil.empty(this.options.target),delete this.video}},this.reload=function(e){var t="player"in this&&"api"in this.player&&this.player.api.currentTime;this.unload(e);var r=mistPlay(this.stream,this.options);if(t&&"live"!=this.info.type){var n=function(){r.player&&r.player.api&&(r.player.api.currentTime=t),this.removeEventListener("initialized",n)};MistUtil.event.addListener(this.options.target,"initialized",n)}return i},this.nextCombo=function(){var e=!1;"player"in this&&"api"in this.player&&(e=this.player.api.currentTime);var t={source:this.source.index,player:this.playerName};if(!this.checkCombo({startCombo:t},!0)){if(!this.checkCombo({startCombo:!1},!0))return;t=!1}this.unload("nextCombo");var r=this.options;if(r.startCombo=t,i=mistPlay(this.stream,r),e&&isFinite(e)&&"live"!=this.info.type){var n=function(){"player"in i&&"api"in i.player&&(i.player.api.currentTime=e),this.removeEventListener("initialized",n)};MistUtil.event.addListener(r.target,"initialized",n)}},this.onPlayerBuilt=function(){},t.MistVideoObject&&(t.MistVideoObject.reference=this),this} \ No newline at end of file +var MistUtil={format:{time:function(e,t){if(isNaN(e)||!isFinite(e))return e;t||(t={});var i=e<0?" ago":"";e=Math.abs(e);var r=Math.floor(e/86400);e-=86400*r;var n=Math.floor(e/3600);e-=3600*n;var a=Math.floor(e/60),s=Math.round(e%1*1e3);e=Math.floor(e-60*a);var o=[];return r&&(r=r+" day"+(r>1?"s":"")+", "),n||r?(o.push(n),o.push(("0"+a).slice(-2))):o.push(a),o.push(("0"+Math.floor(e)).slice(-2)),t.ms&&(o[o.length-1]+="."+("000"+s).slice(-3)),(r||"")+o.join(":")+i},ago:function(e,t){if(isNaN(e.getTime()))return"";var i=t||(new Date).getTime()-e.getTime(),r="",n=i<0;return n&&(i*=-1),i<1e3?r="live":i<6e4?(r=Math.round(i/1e3)+" sec",n?r="in "+r:r+=" ago"):r=!t&&(new Date).toLocaleDateString()==e.toLocaleDateString()||t<864e5?e.toLocaleTimeString(void 0,{hour:"numeric",minute:"2-digit",second:"2-digit"}):i<5184e5?e.toLocaleString(void 0,{weekday:"short",hour:"numeric",minute:"2-digit",second:"2-digit"}):!t&&(new Date).getFullYear()==e.getFullYear()||t<316224e5?e.toLocaleString(void 0,{month:"short",day:"numeric",weekday:"short",hour:"numeric",minute:"2-digit",second:"2-digit"}):e.toLocaleString(void 0,{year:"numeric",month:"short",day:"numeric",hour:"numeric",minute:"2-digit",second:"2-digit"}),r},ucFirst:function(e){return e.charAt(0).toUpperCase()+e.slice(1)},number:function(e){if(isNaN(Number(e))||0==Number(e))return e;var t=Math.max(3,Math.ceil(Math.log(e)/Math.LN10)),i=Math.pow(10,t-Math.floor(Math.log(e)/Math.LN10)-1);if((e=Math.round(e*i)/i)>=1e4){number=e.toString().split(".");for(var r=/(\d+)(\d{3})/;r.test(number[0]);)number[0]=number[0].replace(r,"$1 $2");e=number.join(".")}return e},bytes:function(e,t){if(isNaN(Number(e)))return e;var i=t?["bits","Kb","Mb","Gb","Tb","Pb"]:["bytes","KB","MB","GB","TB","PB"];if(0==e)unit=i[0];else{var r=Math.floor(Math.log(Math.abs(e))/Math.log(1024));r<0?unit=i[0]:(e/=Math.pow(1024,r),unit=i[r])}return this.number(e)+unit},bits:function(e){return this.bytes(e,!0)},mime2human:function(e){switch(e){case"html5/video/webm":return"WebM";case"html5/application/vnd.apple.mpegurl":return"HLS (TS)";case"html5/application/vnd.apple.mpegurl;version=7":return"HLS (CMAF)";case"flash/10":return"Flash (RTMP)";case"flash/11":return"Flash (HDS)";case"flash/7":return"Flash (Progressive)";case"html5/video/mpeg":return"TS";case"html5/application/vnd.ms-sstr+xml":case"html5/application/vnd.ms-ss":return"Smooth Streaming";case"dash/video/mp4":return"DASH";case"webrtc":return"WebRTC (WS)";case"whep":return"WebRTC (WHEP)";case"silverlight":return"Smooth streaming (Silverlight)";case"html5/text/vtt":return"VTT subtitles";case"html5/text/plain":return"SRT subtitles";default:return e.replace("html5/","").replace("video/","").replace("audio/","").toLocaleUpperCase()}}},class:{add:function(e,t){if("classList"in e)e.classList.add(t);else{var i=this.get(e);i.push(t),this.set(e,i)}},remove:function(e,t){if("classList"in e)e.classList.remove(t);else{for(var i=this.get(e),r=i.length-1;r>=0;r--)i[r]==t&&i.splice(r);this.set(e,i)}},get:function(e){var t=e.getAttribute("class");return t&&""!=t?t.split(" "):[]},set:function(e,t){e.setAttribute("class",t.join(" "))},has:function(e,t){return e.className.split(" ").indexOf(t)>=0}},object:{extend:function(e,t,i){for(var r in t)i&&"object"==typeof t[r]&&!("nodeType"in t[r])?(r in e||(MistUtil.array.is(t[r])?e[r]=[]:e[r]={}),this.extend(e[r],t[r],!0)):e[r]=t[r];return e},keys:function(e,t){var i=[];for(var r in e)i.push(r);return t&&("function"!=typeof t&&(t=function(e,t){return e.localeCompare(t)}),i.sort((function(i,r){return t(i,r,e[i],e[r])}))),i},values:function(e,t){var i=this.keys(e,t);for(var r in values=[],i)values.push(e[i[r]]);return values}},array:{indexOf:function(e,t){if(!(e instanceof Array))throw"Tried to use indexOf on something that is not an array";if("indexOf"in e)return e.indexOf(t);for(var i;it?1:e=0?r:i.length}if("function"==typeof e)return e(t);if("object"==typeof e){if(e instanceof Array)return i(t,e[0],e[1]);for(var r in e)return i(t,r,e[r])}if(e in t)return t[e];throw"Invalid sorting rule: "+e+". This should be a function, object or key of "+JSON.stringify(t)+"."}return e.sort((function(e,n){var a=0;for(var s in t){var o=t[s];if(0!=(a=i(r(o,e),r(o,n))))break}return a})),e}},createUnique:function(){var e="uid"+Math.random().toString().replace("0.","");return document.querySelector("."+e)?createUnique():e},http:{getpost:function(e,t,i,r,n){var a=new XMLHttpRequest;if(a.open(e,t,!0),"POST"==e&&a.setRequestHeader("Content-type","application/x-www-form-urlencoded"),n&&(a.timeout=8e3),a.onload=function(){var e=a.status;e>=200&&e<300?r(a.response):n&&n(a)},n&&(a.onerror=function(){n(a)},a.ontimeout=a.onerror),"POST"==e){var s,o=[];for(var l in i)o.push(l+"="+encodeURIComponent(i[l]));o.length&&(s=o.join("&")),a.send(s)}else a.send()},get:function(e,t,i){this.getpost("GET",e,null,t,i)},post:function(e,t,i,r){this.getpost("POST",e,t,i,r)},url:{addParam:function(e,t){var i=e.split("?"),r=[i.shift()],n=[];for(var a in i.length&&(n=i[0].split("&")),t)n.push(a+"="+t[a]);return n.length&&r.push(n.join("&")),r.join("?")},append:function(e,t){var i=document.createElement("a");return i.href=e,"?"==t[0]?""==i.search?i.search=t:i.search+="&"+t.slice(1):"&"==t[0]?""==i.search?i.search="?"+t.slice(1):i.search+=t:i.href+=t,i.href},split:function(e){var t=document.createElement("a");return t.href=e,{protocol:t.protocol,host:t.hostname,hash:t.hash,port:t.port,path:t.pathname.replace(/\/*$/,"")}},sanitizeHost:function(e){var t=MistUtil.http.url.split(e);return t.protocol+"//"+t.host+(t.port&&""!=t.port?":"+t.port:"")+(t.hash&&""!=t.hash?"#"+t.hash:"")+(t.path?"/"==t.path.charAt(0)?t.path:"/"+t.path:"")}}},css:{cache:{},load:function(e,t,i){var r=document.createElement("style");r.type="text/css",r.setAttribute("data-source",e),i&&(r.callback=i);var n=this.cache;function a(e){var i=MistUtil.css.applyColors(e,t);"callback"in r?r.callback(i):r.textContent=i}if(e in n)n[e]instanceof Array?n[e].push(a):a(n[e]);else{n[e]=[a];var s=3;!function t(){MistUtil.http.get(e,(function(t){for(var i in n[e])n[e][i](t);n[e]=t}),(function(){if(s>0)s--,setTimeout(t,2e3);else{var i="/*Failed to load*/";for(var r in n[e])n[e][r](i);n[e]=i}}))}()}return r},applyColors:function(e,t){return e.replace(/\$([^\s^;^}]*)/g,(function(e,i){var r=i.split("."),n=t;for(var a in r)n=n[r[a]];return n}))},createStyle:function(e,t,i){var r=document.createElement("style");return r.type="text/css",e&&(t&&(e=this.prependClass(e,t,i)),r.textContent=e),r},prependClass:function(e,t,i){var r=!1;"string"!=typeof e&&("unprepended"in(r=e)||(r.unprepended=r.textContent),e=r.unprepended);var n=(e=e.replace(/\/\*.*?\*\//g,"")).match(/@[^}]*}/g);for(var a in n){e=e.replace(n[a],"@@#@@");for(var s=1;s0)s=r.bps>131072?Math.round(r.bps/1024/1024*8)+"mbps":Math.round(r.bps/1024*8)+"kbps",n[a]=s;break;case"fpks":r.fpks>0&&(n[a]=r.fpks/1e3+"fps");break;case"channels":r.channels>0&&(n[a]=1==r.channels?"Mono":2==r.channels?"Stereo":"Surround ("+r.channels+"ch)");break;case"rate":n[a]=Math.round(.001*r.rate)+"Khz";break;case"language":"Undetermined"!=r[a]&&(n[a]=r[a]);break;case"codec":if("meta"==r.codec)continue;n[a]=r[a]}r.describe=n}for(var o in t){var l=!1;for(var i in t[o])if(l){if(MistUtil.object.keys(t[o]).length>1)for(var a in t[o][i].describe)l[a]!=t[o][i].describe[a]&&delete l[a]}else l=MistUtil.object.extend({},t[o][i].describe);for(var i in t[o]){var c={},d={};for(var a in t[o][i].describe)a in l?d[a]=t[o][i].describe[a]:c[a]=t[o][i].describe[a];t[o][i].different=c,t[o][i].same=d;var u=MistUtil.object.values(c);t[o][i].displayName=u.length?u.join(", "):MistUtil.object.values(t[o][i].describe).join(" ")}var p={};for(var i in t[o]){if(t[o][i].displayName in p){var h=1;for(var i in t[o])t[o][i].different.trackid=h+")",t[o][i].displayName="Track "+h+" ("+t[o][i].displayName+")",h++;break}p[t[o][i].displayName]=1}}return t},translateCodec:function(e){function t(t){return("0"+e.init.charCodeAt(t).toString(16)).slice(-2)}switch(e.codec){case"AAC":return"mp4a.40.2";case"MP3":return"mp3";case"AC3":return"ec-3";case"H264":return"avc1."+t(1)+t(2)+t(3);case"HEVC":return"hev1."+t(1)+t(6)+t(7)+t(8)+t(9)+t(10)+t(11)+t(12);default:return e.codec.toLowerCase()}}},isTouchDevice:function(){return"ontouchstart"in window||navigator.msMaxTouchPoints>0},getPos:function(e,t){e.currentStyle||window.getComputedStyle(e,null);for(var i=1,r=e;r;)r.style.zoom&&""!=r.style.zoom&&(i*=parseFloat(r.style.zoom,10)),r=r.parentElement;var n=e.getBoundingClientRect().left-(parseInt(e.borderLeftWidth,10)||0),a=e.getBoundingClientRect().width,s=Math.max(0,(t.clientX/i-n)/a);return s=Math.min(s,1)},createGraph:function(e,t){var i="http://www.w3.org/2000/svg",r=document.createElementNS(i,"svg");r.setAttributeNS(null,"height","100%"),r.setAttributeNS(null,"width","100%"),r.setAttributeNS(null,"class","mist icon graph"),r.setAttributeNS(null,"preserveAspectRatio","none");var n=e.x[0],a=e.y[0];if(t.differentiate)for(var s=1;st.x.count&&(l.shift(),d()),d(e.x-n,-1*e.y),this.setAttributeNS(null,"d","M"+l.join(" L")),h()}},r.addData=function(e){m.addData(e)},r},getBrowser:function(){var e=window.navigator.userAgent;return e.indexOf("MSIE ")>=0||e.indexOf("Trident/")>=0?"ie":e.indexOf("Edge/")>=0?"edge":e.indexOf("Opera")>=0||e.indexOf("OPR")>=0?"opera":e.indexOf("Chrome")>=0?"chrome":e.indexOf("Safari")>=0?"safari":e.indexOf("Firefox")>=0&&"firefox"},getAndroid:function(){var e=navigator.userAgent.toLowerCase().match(/android\s([\d\.]*)/i);return!!e&&e[1]},sources:{find:function(e,t){e:for(var i in e){for(var r in t)if("protocol"==r){if(e[i].url.slice(0,t.protocol.length)!=t.protocol)continue e}else if(e[i][r]!=t[r])continue e;return e[i]}return!1}}};if(void 0===MistSkins)var MistSkins={};if("undefined"!=typeof mistoptions&&"host"in mistoptions)var misthost=MistUtil.http.url.sanitizeHost(mistoptions.host);else misthost="..";function MistSkin(e){e.skin=this,this.applySkinOptions=function(t){var i;return"string"==typeof t&&t in MistSkins&&(t=MistUtil.object.extend({},MistSkins[t],!0)),i="inherit"in t&&t.inherit&&t.inherit in MistSkins?this.applySkinOptions(t.inherit):MistSkins.default,this.structure=MistUtil.object.extend({},i.structure),t&&"structure"in t&&MistUtil.object.extend(this.structure,t.structure),this.blueprints=MistUtil.object.extend({},i.blueprints),t&&"blueprints"in t&&MistUtil.object.extend(this.blueprints,t.blueprints),this.icons=MistUtil.object.extend({},i.icons,!0),t&&"icons"in t&&MistUtil.object.extend(this.icons.blueprints,t.icons),this.icons.build=function(t,i,r){i||(i=22);var n,a=this.blueprints[t];n="function"==typeof a.svg?a.svg.call(e,r):a.svg,"object"!=typeof i&&(i={height:i,width:i}),"object"!=typeof a.size&&(a.size={height:a.size,width:a.size}),(!("width"in i)&&"height"in i||!("height"in i)&&"width"in i)&&("width"in i&&(i.height=i.width*a.size.height/a.size.width),"height"in i&&(i.width=i.height*a.size.width/a.size.height));var s="";s+='',s+='',s+=n,s+="",s+="";var o=document.createElement("div");return o.innerHTML=s,o.firstChild},this.colors=MistUtil.object.extend({},i.colors),t&&"colors"in t&&MistUtil.object.extend(this.colors,t.colors,!0),this.css=MistUtil.object.extend({},i.css),t&&"css"in t&&MistUtil.object.extend(this.css,t.css),this},this.applySkinOptions("skin"in e.options?e.options.skin:"default");var t=[];for(var i in this.css)if("string"==typeof this.css[i]){var r=MistUtil.css.load(e.urlappend(this.css[i]),this.colors);t.push(r)}this.css=t}function MistUI(e,t){e.UI=this,this.elements=[],this.buildStructure=function(t){if("function"==typeof t&&(t=t.call(e)),"if"in t){var i=!1;if(t.if.call(e,t)?i=t.then:"else"in t&&(i=t.else),!i)return;for(var r in t)["if","then","else"].indexOf(r)<0&&(r in i?(i[r]instanceof Array||(i[r]=[i[r]]),i[r]=i[r].concat(t[r])):i[r]=t[r]);return this.buildStructure(i)}if("type"in t&&t.type in e.skin.blueprints){var n=e.skin.blueprints[t.type].call(e,t);if(!n)return;if(MistUtil.class.add(n,"mistvideo-"+t.type),"css"in t){var a=MistUtil.createUnique();for(var r in t.css=[].concat(t.css),t.css){var s=MistUtil.css.createStyle(t.css[r],a);n.appendChild(s)}MistUtil.class.add(n,a),n.uid=a}if("classes"in t)for(var r in t.classes)MistUtil.class.add(n,t.classes[r]);if("title"in t&&(n.title=t.title),"style"in t)for(var r in t.style)n.style[r]=t.style[r];if("children"in t)for(var r in t.children){var o=this.buildStructure(t.children[r]);o&&n.appendChild(o)}return e.UI.elements.push(n),n}return!1},this.build=function(){return this.buildStructure(t||e.skin.structure.main)};var i=this.build(),r=MistUtil.createUnique(),n=0;for(var a in e.skin.css.length&&(i.style.opacity=0),e.skin.css){var s=e.skin.css[a];s.callback=function(t){"/*Failed to load*/"==t?(this.textContent=t,e.showError("Failed to load CSS from "+this.getAttribute("data-source"))):this.textContent=MistUtil.css.prependClass(t,r,!0),n++,e.skin.css.length<=n&&(i.style.opacity="")},""!=s.textContent&&s.callback(s.textContent),i.appendChild(s)}MistUtil.class.add(i,r);var o=MistUtil.getBrowser();return o&&MistUtil.class.add(i,"browser-"+o),i}MistSkins.default={structure:{main:{if:function(){return!!this.info.hasVideo&&"audio"!=this.source.type.split("/")[1]},then:{type:"placeholder",classes:["mistvideo"],children:[{type:"hoverWindow",classes:["mistvideo-maincontainer"],mode:"pos",style:{position:"relative"},transition:{hide:"left: 0; right: 0; bottom: -43px;",show:"bottom: 0;",viewport:"left:0; right: 0; top: -1000px; bottom: 0;"},button:{type:"videocontainer"},children:[{type:"loading"},{type:"error"}],window:{type:"controls"}}]},else:{type:"container",classes:["mistvideo"],style:{overflow:"visible"},children:[{type:"controls",classes:["mistvideo-novideo"],style:{width:"480px"}},{type:"loading"},{type:"error"},{if:function(){return"stock"==this.options.controls},then:{type:"video",style:{position:"absolute"}},else:{type:"video",style:{position:"absolute",display:"none"}}}]}},videocontainer:{type:"container",children:[{type:"videobackground",alwaysDisplay:!1,delay:5},{type:"video"},{type:"subtitles"}]},controls:{if:function(){return!!(this.player&&this.player.api&&this.player.api.play)},then:{type:"container",classes:["mistvideo-column"],children:[{type:"progress",classes:["mistvideo-pointer"]},{type:"container",classes:["mistvideo-main","mistvideo-padding","mistvideo-row","mistvideo-background"],children:[{type:"play",classes:["mistvideo-pointer"]},{type:"currentTime"},{if:function(){return"size"in this&&this.size.width>300||!this.info.hasVideo||"audio"==this.source.type.split("/")[1]},then:{type:"totalTime"}},{type:"container",classes:["mistvideo-align-right"],children:[{type:"container",children:[{type:"container",classes:["mistvideo-volume_container"],children:[{type:"volume",mode:"horizontal",size:{height:22},classes:["mistvideo-pointer"]}]},{type:"speaker",classes:["mistvideo-pointer"],style:{"margin-left":"-2px"}}]},{if:function(){return"size"in this&&this.size.width>300||!this.info.hasVideo||"audio"==this.source.type.split("/")[1]},then:{type:"container",children:[{type:"chromecast",classes:["mistvideo-pointer"]},{type:"loop",classes:["mistvideo-pointer"]},{type:"fullscreen",classes:["mistvideo-pointer"]},{type:"picture-in-picture",classes:["mistvideo-pointer"]}]}},{type:"hoverWindow",mode:"pos",transition:{hide:"right: -1000px; bottom: 44px;",show:"right: 5px;",viewport:"right: 0; left: 0; bottom: 0; top: -1000px"},button:{type:"settings",classes:["mistvideo-pointer"]},window:{type:"submenu"}}]}]}]},else:{if:function(){return!(!this.player||!this.player.api)},then:{type:"hoverWindow",mode:"pos",transition:{hide:"right: -1000px; bottom: 44px;",show:"right: 2.5px;",viewport:"right: 0; left: -1000px; bottom: 0; top: -1000px"},style:{right:"5px",left:"auto"},button:{type:"settings",classes:["mistvideo-background","mistvideo-padding"]},window:{type:"submenu"}}}},submenu:{type:"container",style:{width:"80%",maxWidth:"25em",zIndex:2},classes:["mistvideo-padding","mistvideo-column","mistvideo-background"],children:[{type:"tracks"},{if:function(){return"size"in this&&this.size.width<=300},then:{type:"container",classes:["mistvideo-center"],children:[{type:"chromecast",classes:["mistvideo-pointer"]},{type:"loop",classes:["mistvideo-pointer"]},{type:"fullscreen",classes:["mistvideo-pointer"]},{type:"picture-in-picture",classes:["mistvideo-pointer"]}]}}]},placeholder:{type:"container",classes:["mistvideo","mistvideo-delay-display"],children:[{type:"placeholder"},{type:"loading"},{type:"error"}]},secondaryVideo:function(e){return{type:"hoverWindow",classes:["mistvideo"],mode:"pos",transition:{hide:"left: 10px; bottom: -40px;",show:"bottom: 10px;",viewport:"left: 0; right: 0; top: 0; bottom: 0"},button:{type:"container",children:[{type:"videocontainer"}]},window:{type:"switchVideo",classes:["mistvideo-controls","mistvideo-padding","mistvideo-background","mistvideo-pointer"],containers:e}}}},css:{skin:misthost+"/skins/default.css"},icons:{blueprints:{play:{size:45,svg:''},largeplay:{size:45,svg:''},pause:{size:45,svg:''},speaker:{size:45,svg:''},volume:{size:{width:100,height:45},svg:function(){var e=MistUtil.createUnique();return''}},muted:{size:45,svg:''},fullscreen:{size:45,svg:''},pip:{size:45,svg:''},loop:{size:45,svg:''},settings:{size:45,svg:''},loading:{size:100,svg:''},timeout:{size:25,svg:function(e){e&&e.delay||(e={delay:10});var t=e.delay,i=MistUtil.createUnique();return''}},popout:{size:45,svg:''},switchvideo:{size:45,svg:''}}},blueprints:{container:function(){return document.createElement("div")},video:function(){var e=this;if(MistUtil.event.addListener(e.video,"contextmenu",(function(t){t.preventDefault(),e.container.setAttribute("data-show-submenu",""),e.container.removeAttribute("data-hide-submenu"),e.container.removeAttribute("data-hidecursor");var i=function(){e.container.removeAttribute("data-show-submenu"),e.container.removeEventListener("mouseout",i)};MistUtil.event.addListener(e.container,"mouseout",i)})),e.video.hideTimer=!1,e.video.hideCursor=function(){this.hideTimer&&clearTimeout(this.hideTimer),this.hideTimer=e.timers.start((function(){e.container.setAttribute("data-hidecursor","");var t=e.container.querySelector(".mistvideo-controls");t&&t.parentNode.setAttribute("data-hidecursor","")}),3e3)},MistUtil.event.addListener(e.video,"mousemove",(function(){e.container.removeAttribute("data-hidecursor");var t=e.container.querySelector(".mistvideo-controls");t&&t.parentNode.removeAttribute("data-hidecursor"),e.video.hideCursor()})),MistUtil.event.addListener(e.video,"mouseout",(function(){e.video.hideTimer&&e.timers.stop(e.video.hideTimer)})),e.options.autoplay)var t=MistUtil.event.addListener(e.video,"canplay",(function(){if(e.player.api&&e.player.api.paused){var i=e.player.api.play();i&&i.catch((function(t){if(!e.destroyed)if(e.log("Autoplay failed. Retrying with muted audio.."),e.info.hasVideo){e.player.api.muted=!0,MistUtil.event.send("volumechange",null,e.video);var i=e.player.api.play();i&&i.then((function(){e.reporting&&(e.reporting.stats.d.autoplay="success")})).then((function(){if(!e.destroyed){e.log("Autoplay worked! Video will be unmuted on mouseover if the page has been interacted with."),e.reporting&&(e.reporting.stats.d.autoplay="muted");var t=e.skin.icons.build("muted",100);MistUtil.class.add(t,"mistvideo-pointer"),e.container.appendChild(t),MistUtil.event.addListener(t,"click",(function(){e.player.api.muted=!1,e.container.removeChild(t)}));var i=!1,r=function(){i=!0,document.body.removeEventListener("click",r)};MistUtil.event.addListener(document.body,"click",r,e.video);var n=function(){i&&(e.player.api.muted=!1,e.video.removeEventListener("mouseenter",n),e.log("Re-enabled sound"))};MistUtil.event.addListener(e.video,"mouseenter",n);var a=function(){e.player.api.muted||(t.parentNode&&e.container.removeChild(t),e.video.removeEventListener("volumechange",a),document.body.removeEventListener("click",r),e.video.removeEventListener("mouseenter",n))};MistUtil.event.addListener(e.video,"volumechange",a)}})).catch((function(){if(!e.destroyed){e.log("Autoplay failed even with muted video. Unmuting and showing play button."),e.timers.start((function(){e.player.api.paused&&(e.player.api.pause(),e.monitor&&e.monitor.destroy())}),5e3),e.reporting&&(e.reporting.stats.d.autoplay="failed"),e.player.api.muted=!1;var t=e.skin.icons.build("largeplay",150);MistUtil.class.add(t,"mistvideo-pointer"),e.container.appendChild(t),MistUtil.event.addListener(t,"click",(function(){e.player.api.paused&&e.player.api.play()}));var i=function(){e.container.removeChild(t),e.video.removeEventListener("play",i)};MistUtil.event.addListener(e.video,"play",i)}}))}else e.reporting&&(e.reporting.stats.d.autoplay="failed")}))}else e.reporting&&(e.reporting.stats.d.autoplay="success");MistUtil.event.removeListener(t)}));return this.video},videocontainer:function(){return this.UI.buildStructure(this.skin.structure.videocontainer)},secondaryVideo:function(e){e||(e={}),e.options||(e.options={});var t=this;"secondary"in t||(t.secondary=[]);var i=MistUtil.object.extend({},t.options);i=MistUtil.object.extend(i,e.options),t.secondary.push(i);var r={primary:t,secondary:!1};i.target=document.createElement("div"),delete i.container;var n={};return i.MistVideoObject=n,MistUtil.event.addListener(i.target,"initialized",(function(){var e=n.reference;i.MistVideo=e,r.secondary=e,e.player.api.muted=!0,e.player.api.loop=!1;for(var a=i.target.querySelectorAll(".mistvideo-controls"),s=0;s30)e.player.api.pausedesync=!0,e.player.api.currentTime=this.currentTime,e.log("Re-syncing with main video by seeking (desync: "+t+"s)");else if(i>.01){var r=.1;i<1&&(r=.05),(r=1+r*Math.sign(t))!=e.player.api.playbackRate&&e.log("Re-syncing by changing the playback rate (desync: "+Math.round(1e3*t)+"ms, rate: "+r+")"),e.player.api.playbackRate=r}else 1!=e.player.api.playbackRate&&(e.player.api.playbackRate=1,e.log("Sync with main video achieved (desync: "+Math.round(1e3*t)+"ms)"))}}),i.target),MistUtil.event.addListener(e.video,"seeked",(function(){e.player.api.pausedesync=!1}))})),i.skin=MistUtil.object.extend({},t.skin,!0),i.skin.structure.main=MistUtil.object.extend({},t.skin.structure.secondaryVideo(r)),mistPlay(t.stream,i),i.target},switchVideo:function(e){var t=document.createElement("div");return t.appendChild(this.skin.icons.build("switchvideo")),MistUtil.event.addListener(t,"click",(function(){var t=e.containers.primary,i=e.containers.secondary;function r(e,t){if(e.video.currentTarget==t)return e.video;if(e.secondary)for(var i=0;i300&&(e.style.zoom=1.5),e}},submenu:function(){return this.UI.buildStructure(this.skin.structure.submenu)},hoverWindow:function(e){var t={type:"container",classes:"classes"in e?e.classes:[],children:"children"in e?e.children:[]};switch(t.classes.push("hover_window_container"),"classes"in e.window||(e.window.classes=[]),e.window.classes.push("inner_window"),e.window.classes.push("mistvideo-container"),e.window={type:"container",classes:["outer_window"],children:[e.window]},"classes"in e.button||(e.button.classes=[]),e.button.classes.push("pointer"),e.mode){case"left":t.classes.push("horizontal"),t.children=[e.window,e.button];break;case"right":t.classes.push("horizontal"),t.children=[e.button,e.window];break;case"top":t.classes.push("vertical"),t.children=[e.button,e.window];break;case"bottom":t.classes.push("vertical"),t.children=[e.window,e.button];break;case"pos":t.children=[e.button,e.window],"classes"in e.window||(e.window.classes=[]);break;default:throw"Unsupported mode for structure type hoverWindow"}return"transition"in e&&("css"in t||(t.css=[]),t.css.push(".hover_window_container:hover > .outer_window:not([data-hidecursor]) > .inner_window { "+e.transition.show+" }\n.hover_window_container > .outer_window { "+e.transition.viewport+" }\n.hover_window_container > .outer_window > .inner_window { "+e.transition.hide+" }")),t.classes.push(e.mode),this.UI.buildStructure(t)},draggable:function(e){var t=this.skin.blueprints.container(e),i=this,r=this.skin.icons.build("fullscreen",16);MistUtil.class.remove(r,"fullscreen"),MistUtil.class.add(r,"draggable-icon"),t.appendChild(r),r.style.alignSelf="flex-end",r.style.position="absolute",r.style.cursor="move";var n={},a=function(e){t.style.left=e.clientX-n.x+"px",t.style.top=e.clientY-n.y+"px"},s=function(e){window.removeEventListener("mousemove",a),window.removeEventListener("click",s),MistUtil.event.addListener(r,"click",o)},o=function(e){e.stopPropagation(),r.removeEventListener("click",o),n.x=i.container.getBoundingClientRect().left-(t.getBoundingClientRect().left-e.clientX),n.y=i.container.getBoundingClientRect().top-(t.getBoundingClientRect().top-e.clientY),t.style.position="absolute",t.style.right="auto",t.style.bottom="auto",i.container.appendChild(t),a(e),MistUtil.event.addListener(window,"mousemove",a,t),MistUtil.event.addListener(window,"click",s,t)};return MistUtil.event.addListener(r,"click",o),t},progress:function(){var e=document.createElement("div"),t=document.createElement("div");e.appendChild(t),t.kids={},t.kids.bar=document.createElement("div"),t.kids.bar.className="bar",t.appendChild(t.kids.bar);var i=this.video,r=this,n=1/0;if(r.info&&r.info.meta&&r.info.meta.tracks)for(var a in r.info.meta.tracks).001*r.info.meta.tracks[a].firstms1e3?(t.updateBuffers(r.player.api.buffered),l=(new Date).getTime()):c||(c=r.timers.start((function(){e(),c=!1}),1e3))}()}),t);var d=0,u=!1;MistUtil.event.addListener(i,"timeupdate",(function(){!function e(){(new Date).getTime()-d>200&&!f?(t.updateBar(r.player.api.currentTime),d=(new Date).getTime()):u||(u=r.timers.start((function(){e(),u=!1}),1e3))}()}),t),MistUtil.event.addListener(i,"seeking",(function(){t.updateBar(r.player.api.currentTime)}),t),t.getPos=function(e){var t=isNaN(e)?MistUtil.getPos(this,e):e;return"live"==r.info.type?(t-1)*o()+r.player.api.duration:!!isFinite(r.player.api.duration)&&t*(r.player.api.duration-s())+s()},t.seek=function(e){var t=this.getPos(e);r.player.api.currentTime=t},MistUtil.event.addListener(e,"mouseup",(function(e){1==e.which&&t.seek(e)}));var p=r.UI.buildStructure({type:"tooltip"});p.style.opacity=0,t.appendChild(p),MistUtil.event.addListener(e,"mouseout",(function(){f||(p.style.opacity=0)})),t.moveTooltip=function(e){var t=this.getPos(e);if(!1!==t){p.setDisplay(t),p.style.opacity=1;var i=MistUtil.getPos(this,e),r={bottom:20};i>.5?(r.right=100*(1-i)+"%",p.triangle.setMode("bottom","right")):(r.left=100*i+"%",p.triangle.setMode("bottom","left")),p.setPos(r)}else p.style.opacity=0};var h=document.createElement("span");h.setAttribute("class","mistvideo-realtime");var m=document.createTextNode("");h.appendChild(m),p.setDisplay=function(e){if(r.options.useDateTime&&r.info&&r.info.unixoffset){var i=t.getPos(1)-t.getPos(0),n=.001*(new Date).getTime()-(.001*r.info.unixoffset+t.getPos(1)),a=Math.max(i,n),s="";if("live"==r.info.type)if(a<60)s=MistUtil.format.ago(new Date(r.info.unixoffset+1e3*e));else{var o=.001*(new Date).getTime()-(.001*r.info.unixoffset+e);o<172800&&(s+=" - "+MistUtil.format.time(o))}else s+=MistUtil.format.time(e);if(a>=60){m.nodeValue=" at "+MistUtil.format.ago(new Date(r.info.unixoffset+1e3*e),1e3*a);var l=document.createDocumentFragment();l.appendChild(document.createTextNode(s)),l.appendChild(h),p.setHtml(l)}else m.nodeValue="",p.setText(s)}else p.setText(MistUtil.format.time(e))},MistUtil.event.addListener(e,"mousemove",(function(e){t.moveTooltip(e)}));var f=!1;return MistUtil.event.addListener(e,"mousedown",(function(i){if(1==i.which){f=!0,t.updateBar(t.getPos(i));var r=MistUtil.event.addListener(document,"mousemove",(function(e){t.updateBar(t.getPos(e)),t.moveTooltip(e)}),t),n=MistUtil.event.addListener(document,"mouseup",(function(i){1==i.which&&(f=!1,MistUtil.event.removeListener(r),MistUtil.event.removeListener(n),p.style.opacity=0,(!i.path||MistUtil.array.indexOf(i.path,e)<0)&&t.seek(i))}),t)}})),e},play:function(){var e=this,t=document.createElement("div");t.appendChild(this.skin.icons.build("play")),t.appendChild(this.skin.icons.build("pause")),t.setState=function(e){this.setAttribute("data-state",e)},t.setState("paused");var i=this.video;return MistUtil.event.addListener(i,"playing",(function(){t.setState("playing"),e.options.autoplay=!0}),t),MistUtil.event.addListener(i,"pause",(function(){t.setState("paused")}),t),MistUtil.event.addListener(i,"paused",(function(){t.setState("paused")}),t),MistUtil.event.addListener(i,"ended",(function(){t.setState("paused")}),t),MistUtil.event.addListener(t,"click",(function(){e.player.api.error&&e.player.api.load(),e.player.api.paused?e.player.api.play():(e.player.api.pause(),e.options.autoplay=!1)})),e.player.api&&MistUtil.event.addListener(e.video,"click",(function(){e.player.api.paused?e.player.api.play():MistUtil.isTouchDevice()||(e.player.api.pause(),e.options.autoplay=!1)}),t),t},speaker:function(){if(!this.player.api||!("muted"in this.player.api))return!1;var e=!1,t=this.info.meta.tracks;for(var i in t)if("audio"==t[i].type){e=!0;break}if(!e)return!1;var r=this.skin.icons.build("speaker"),n=this,a=this.video;return n.player.api.volume&&!n.player.api.muted||MistUtil.class.add(r,"off"),MistUtil.event.addListener(a,"volumechange",(function(){n.player.api.volume&&!n.player.api.muted?MistUtil.class.remove(r,"off"):MistUtil.class.add(r,"off")}),r),MistUtil.event.addListener(r,"click",(function(e){n.player.api.muted=!n.player.api.muted})),r},volume:function(e){if(!this.player.api||!("volume"in this.player.api))return!1;var t=!1,i=this.info.meta.tracks;for(var r in i)if("audio"==i[r].type){t=!0;break}if(!t)return!1;var n=document.createElement("div"),a=this.skin.icons.build("volume","size"in e&&e.size);n.appendChild(a);var s=this;a.mode="mode"in e?e.mode:"vertical","vertical"==a.mode&&(a.style.transform="rotate(90deg)"),a.margin={start:.15,end:.1};var o=this.video;a.set=function(e){100!=(e=100-100*Math.pow(1-e/100,2))&&0!=e&&(e=100*this.addPadding(e/100));for(var t=a.querySelectorAll(".slider"),i=0;i6e4&&e.size.width>=600&&(n.nodeValue=" (at "+MistUtil.format.ago(o)+")")),t.setAttribute("title",MistUtil.format.ago(o,3456e7))}else r=a(s),t.setAttribute("title",r);i.nodeValue=r},t.set(),MistUtil.event.addListener(e.video,"timeupdate",(function(){t.set()}),t),MistUtil.event.addListener(e.video,"seeking",(function(){t.set()}),t),t},totalTime:function(){var e=this,t=document.createElement("div"),i=document.createTextNode("");t.appendChild(i);this.player.api;return"live"==e.info.type?(i.nodeValue="live",t.className="live"):(t.set=function(r){if(!isNaN(r)&&isFinite(r))if(this.style.display="",e.options.useDateTime&&e.info&&"live"==e.info.type&&e.info.unixoffset){var n=new Date(1e3*r+e.info.unixoffset);i.nodeValue=MistUtil.format.ago(n),t.setAttribute("title",MistUtil.format.ago(n,3456e7))}else i.nodeValue=MistUtil.format.time(r),t.setAttribute("title",i.nodeValue);else this.style.display="none"},MistUtil.event.addListener(e.video,"durationchange",(function(){var i=e.player.api.duration;t.set(i)}),t)),t},playername:function(){if(this.playerName&&this.playerName in mistplayers){var e=document.createElement("span");return e.appendChild(document.createTextNode(mistplayers[this.playerName].name)),e}},mimetype:function(){if(this.source){var e=document.createElement("a");return e.href=this.source.url,e.target="_blank",e.title=e.href+" ("+this.source.type+")",e.appendChild(document.createTextNode(MistUtil.format.mime2human(this.source.type))),e}},logo:function(e){if("element"in e)return e.element;if("src"in e){var t=document.createElement("img");return t.src=e.src,t}},settings:function(){var e=this,t=this.skin.icons.build("settings"),i=void 0!==document.ontouchstart;return MistUtil.event.addListener(t,"click",(function(){e.container.hasAttribute("data-show-submenu")?(i&&e.container.setAttribute("data-hide-submenu",""),e.container.removeAttribute("data-show-submenu")):(e.container.setAttribute("data-show-submenu",""),e.container.removeAttribute("data-hide-submenu"))})),t},loop:function(){if("loop"in this.player.api&&"live"!=this.info.type){var e=this,t=this.skin.icons.build("loop");this.video;return t.set=function(){e.player.api.loop?MistUtil.class.remove(this,"off"):MistUtil.class.add(this,"off")},MistUtil.event.addListener(t,"click",(function(t){e.player.api.loop=!e.player.api.loop,this.set()})),t.set(),t}},fullscreen:function(){if("setSize"in this.player&&this.info.hasVideo&&"audio"!=this.source.type.split("/")[1]){var e=this,t=["requestFullscreen","webkitRequestFullscreen","mozRequestFullScreen","msRequestFullscreen","webkitEnterFullscreen"],i=[function(){return e.container},function(){return e.video}],r=!1;e:for(var n in i)for(var a in t)if(t[a]in i[n]()){(r={}).request=function(){return r.fullscreenableElement()[t[a]]()};var s=["exitFullscreen","webkitCancelFullScreen","mozCancelFullScreen","msExitFullscreen","webkitExitFullscreen"],o=["fullscreenElement","webkitFullscreenElement","mozFullScreenElement","msFullscreenElement","webkitFullscreenElement"];r.cancel=function(){return document[s[a]]()},r.element=function(){return document[o[a]]},r.event=["fullscreenchange","webkitfullscreenchange","mozfullscreenchange","MSFullscreenChange","webkitfullscreenchange"][a],r.fullscreenableElement=i[n];break e}if(!r){var l=function(e){if("Escape"===e.key)r.cancel()};(r={event:"fakefullscreenchange",fullscreenableElement:function(){return e.container}}).request=function(){return r.element=function(){return e.container},MistUtil.event.send(r.event,null,document),document.addEventListener("keydown",l),!0},r.cancel=function(){return r.element=function(){return null},document.removeEventListener("keydown",l),MistUtil.event.send(r.event,null,document),!0},r.element=function(){return null}}var c=this.skin.icons.build("fullscreen");return MistUtil.event.addListener(c,"click",d),MistUtil.event.addListener(e.video,"dblclick",d),MistUtil.event.addListener(document,r.event,(function(){r.element()==r.fullscreenableElement()?e.container.setAttribute("data-fullscreen",""):e.container.hasAttribute("data-fullscreen")&&e.container.removeAttribute("data-fullscreen"),e.player.resizeAll()}),c),c}function d(){r.element()?r.cancel():r.request()}},"picture-in-picture":function(){if("setSize"in this.player&&this.info.hasVideo&&"audio"!=this.source.type.split("/")[1]&&document.pictureInPictureEnabled){var e=this;if("requestPictureInPicture"in e.video){var t=this.skin.icons.build("pip");return t.set=function(){document.pictureInPictureElement?MistUtil.class.remove(this,"off"):MistUtil.class.add(this,"off")},MistUtil.event.addListener(t,"click",(function(){var i;(i=document.pictureInPictureElement?document.exitPictureInPicture():e.video.requestPictureInPicture())?i.then((function(){t.set()})):t.set()})),t.set(),t}}},tracks:function(){if(this.info&&this.video){var e=this,t=document.createElement("table");return i(this.info.meta.tracks),MistUtil.event.addListener(e.video,"metaUpdate_tracks",(function(e){i(e.message.meta.tracks)}),t),t}function i(i){MistUtil.empty(t),i=MistUtil.tracks.parse(i);var r={},n={};function a(t,i){if(i?e.log("User selected "+t+" track with id "+i):(e.log("User selected automatic track selection for "+t),MistUtil.event.send("trackSetToAuto",t,e.video)),e.options.setTracks||(e.options.setTracks={}),e.options.setTracks[t]=i,!0===i&&r[t]&&MistUtil.event.send("change",null,r[t]),"setTrack"in e.player.api)return e.player.api.setTrack(t,i);var n={};for(var a in r)"subtitle"!=a&&""!=r[a].value&&(n[a]=r[a].value);return""!=i&&(n[t]=i),"setTracks"in e.player.api?e.player.api.setTracks(n):"setSource"in e.player.api?e.player.api.setSource(MistUtil.http.url.addParam(e.source.url,n)):void 0}var s=MistUtil.object.keys(i,(function(e,t){function i(e){switch(e){case"audio":return"aaaaaaa";case"video":return"aaaaaab";default:return e}}return i(e)>i(t)?1:i(e)1&&"player"in e&&"api"in e.player&&("setTrack"in e.player.api||"setTracks"in e.player.api||"setSource"in e.player.api)){var b=document.createElement("select");if(b.title="Select another "+l+" track",r[l]=b,b.trackType=l,f.appendChild(b),"subtitle"!=l){var k=document.createElement("option");b.appendChild(k),k.value="",k.appendChild(document.createTextNode("Automatic"))}var M=S(c[MistUtil.object.keys(c)[0]].same);if(M.length)(x=document.createElement("span")).className="mistvideo-description",f.appendChild(x),f.appendChild(document.createTextNode(M.join(" ")));function w(e){return""==e?-1:Number(e)}var U=MistUtil.object.keys(c,(function(e,t){return w(e)-w(t)}));for(var p in U){var C=c[U[p]];k=document.createElement("option");b.appendChild(k),k.value="idx"in C?C.idx:C.trackid,MistUtil.object.keys(C.different).length?k.appendChild(document.createTextNode(S(C.different).join(" "))):k.appendChild(document.createTextNode("Track "+(Number(p)+1)))}if(MistUtil.event.addListener(e.video,"playerUpdate_trackChanged",(function(t){t.message.type==l&&"none"!=t.message.trackid&&(b.value=t.message.trackid,e.log("Player selected "+l+" track with id "+t.message.trackid))}),b),"subtitle"==l){if(MistUtil.event.addListener(b,"change",(function(){try{localStorage.mistSubtitleLanguage=c[this.value].lang}catch(e){}if("setWSSubtitle"in e.player.api)e.player.api.setWSSubtitle(""==this.value?void 0:this.value);else if(""!=this.value){var t=MistUtil.object.extend({},c[this.value]);t.label=S(t.describe).join(" "),t.src=MistUtil.http.url.addParam(u,{track:this.value}),e.player.api.setSubtitle(t)}else e.player.api.setSubtitle()})),"localStorage"in window&&null!=localStorage&&"mistSubtitleLanguage"in localStorage)for(var p in c)if(c[p].lang==localStorage.mistSubtitleLanguage){b.value=p;var T=document.createEvent("Event");T.initEvent("change"),b.dispatchEvent(T);break}}else MistUtil.event.addListener(b,"change",(function(){this.trackType in n&&(n[this.trackType].checked=!0),a(this.trackType,this.value)}))}else{var x;(x=document.createElement("span")).className="mistvideo-description",f.appendChild(x),x.appendChild(document.createTextNode(S(c[g[0]].same).join(" ")))}}function S(e){var t={trackid:0,language:1,width:2,bps:3,fpks:4,channels:5,codec:6,rate:7};return MistUtil.object.values(e,(function(e,i,r,n){return t[e]>t[i]?1:t[e]=.999?n():a()}),1e3))}var s=["waiting","seeking","stalled"];for(var o in s)MistUtil.event.addListener(e.video,s[o],(function(t){e.player.api&&!e.player.api.paused&&"container"in e&&r(t)}),t);s=["seeked","playing","canplay","paused","ended"];for(var o in s)MistUtil.event.addListener(e.video,s[o],(function(t){"container"in e&&n()}),t);MistUtil.event.addListener(e.video,"progress",(function(t){"container"in e&&"monitor"in e&&"vars"in e.monitor&&"score"in e.monitor.vars&&e.monitor.vars.score>.99&&n()}),t)}return t},error:function(){var e=this,t=document.createElement("div");t.message=function(t,i,r){MistUtil.empty(this);var n=document.createElement("div");if(n.className="message",this.appendChild(n),!r.polling&&!r.passive&&!r.hideTitle){var a=document.createElement("h3");n.appendChild(a),a.appendChild(document.createTextNode("The "+(e.casting?"chromecast":"player")+" has encountered a problem"))}var s=document.createElement("p");if(n.appendChild(s),n.update=function(e){MistUtil.empty(s),s.innerHTML=e},t){e.info.on_error&&(t=e.info.on_error.replace(/\/,t)),n.update(t);var o=document.createElement("p");if(o.className="details mistvideo-description",n.appendChild(o),i)o.appendChild(document.createTextNode(i));else if("decodingIssues"in e.skin.blueprints){if("player"in e&&"api"in e.player&&e.video){if(i=[],void 0!==e.state&&i.push(["Stream state:",e.state]),void 0!==e.player.api.currentTime&&i.push(["Current video time:",MistUtil.format.time(e.player.api.currentTime)]),"video"in e&&"getVideoPlaybackQuality"in e.video){var l=e.video.getVideoPlaybackQuality();"droppedVideoFrames"in l&&"totalVideoFrames"in l&&l.totalVideoFrames&&i.push(["Frames dropped/total:",MistUtil.format.number(l.droppedVideoFrames)+"/"+MistUtil.format.number(l.totalVideoFrames)]),"corruptedVideoFrames"in l&&l.corruptedVideoFrames&&i.push(["Corrupted frames:",MistUtil.format.number(l.corruptedVideoFrames)])}i.push({0:["NETWORK EMPTY:","not yet initialized"],1:["NETWORK IDLE:","resource selected, but not in use"],2:["NETWORK LOADING:","data is being downloaded"],3:["NETWORK NO SOURCE:","could not locate source"]}[e.video.networkState]);if(i.push({0:["HAVE NOTHING:","no information about ready state"],1:["HAVE METADATA:","metadata has been loaded"],2:["HAVE CURRENT DATA:","data for the current playback position is available, but not for the next frame"],3:["HAVE FUTURE DATA:","data for current and next frame is available"],4:["HAVE ENOUGH DATA:","can start playing"]}[e.video.readyState]),!r.passive){var c=document.createElement("table");for(var d in i){var u=document.createElement("tr");for(var p in c.appendChild(u),i[d]){var h=document.createElement("td");u.appendChild(h),h.appendChild(document.createTextNode(i[d][p]))}}o.appendChild(c)}}var m,f=document.createElement("div");f.className="mistvideo-container mistvideo-column",f.style.textAlign="left",f.style.marginBottom="1em",n.appendChild(f),(m=e.UI.buildStructure({type:"forcePlayer"}))&&f.appendChild(m),(m=e.UI.buildStructure({type:"forceType"}))&&f.appendChild(m)}}return n};var i,r=!1,n=!1,a={};if(this.showError=function(s,o){o||(o={softReload:!!(e.player&&e.player.api&&e.player.api.load),reload:!0,nextCombo:!!e.info,polling:!1,passive:!1});var l=o.type?o.type:s;if(!(l in a)){if(!0===o.reload&&(e.options.reloadDelay&&!isNaN(Number(e.options.reloadDelay))?o.reload=Number(e.options.reloadDelay):o.reload=10),o.passive){if(!0===r)return;if(r)return i.update(s),void(n=(new Date).getTime());t.setAttribute("data-passive","")}else t.removeAttribute("data-passive");var c;r&&t.clear(),r=!o.passive||"passive",n=(new Date).getTime(),e.casting||(c=this.log(s,"error"));var d=t.message(s,!1,o);i=d;var u=document.createElement("div");if(u.className="mistvideo-buttoncontainer",d.appendChild(u),MistUtil.empty(u),e.casting&&!o.passive){var p={type:"button",label:"Stop casting",onclick:function(){e.detachFromCast()}};isNaN(o.softReload+"")||(p.delay=o.softReload),u.appendChild(e.UI.buildStructure(p))}if(o.softReload&&!e.casting){p={type:"button",label:"Reload video",onclick:function(){e.player.api.load()}};isNaN(o.softReload+"")||(p.delay=o.softReload),u.appendChild(e.UI.buildStructure(p))}if(o.reload){p={type:"button",label:"Reload player",onclick:function(){e.reload("Reloading because reload button was clicked.")}};isNaN(o.reload+"")||(p.delay=o.reload),u.appendChild(e.UI.buildStructure(p))}if(o.nextCombo){p={type:"button",label:"Next source",onclick:function(){e.nextCombo()}};isNaN(o.nextCombo+"")||(p.delay=o.nextCombo),u.appendChild(e.UI.buildStructure(p))}if(o.ignore){p={type:"button",label:"Ignore",onclick:function(){this.clearError(),a[l]=!0}};isNaN(o.ignore+"")||(p.delay=o.ignore),u.appendChild(e.UI.buildStructure(p))}o.polling&&u.appendChild(e.UI.buildStructure({type:"polling"})),MistUtil.class.add(t,"show"),"container"in e&&e.container.removeAttribute("data-loading"),c&&c.defaultPrevented&&(e.log("Error event was defaultPrevented, not showing."),t.clear())}},t.clear=function(){for(var i=t.querySelectorAll("svg.icon.timeout"),n=0;n=0;e--)r.removeChild(r.children[e])},e.setHtml=function(n){r.empty(),r.appendChild(n),"html"!=t&&(e.removeChild(i),e.appendChild(r),t="html")};var n=document.createElement("div");return e.triangle=n,n.className="triangle",e.appendChild(n),n.setMode=function(e,t){e||(e="bottom"),t||(t="left");var i=["bottom","top","right","left"];for(var r in i){this.style[i[r]]="";var n=MistUtil.format.ucFirst(i[r]);this.style["border"+n]="",this.style["border"+n+"Color"]=""}var a={top:"bottom",bottom:"top",left:"right",right:"left"};this.style[e]="-10px",this.style["border"+MistUtil.format.ucFirst(a[e])]="none",this.style["border"+MistUtil.format.ucFirst(e)+"Color"]="transparent",this.style[t]=0,this.style["border"+MistUtil.format.ucFirst(a[t])]="none"},e.setPos=function(e){var t={left:"auto",right:"auto",top:"auto",bottom:"auto"};for(var i in MistUtil.object.extend(t,e),t)isNaN(t[i])||(t[i]+="px"),this.style[i]=t[i]},e},button:function(e){var t=document.createElement("button"),i=this;if(e.onclick&&(MistUtil.event.addListener(t,"click",(function(){e.onclick.call(i,arguments)})),e.delay)){var r=this.UI.buildStructure({type:"timeout",delay:e.delay,function:e.onclick});r&&t.appendChild(r)}return t.appendChild(document.createTextNode(e.label)),t},videobackground:function(e){e||(e={}),e.delay||(e.delay=5);for(var t=document.createElement("div"),i=this,r=[],n=0;n<2;n++){var a=document.createElement("canvas");a._context=a.getContext("2d"),t.appendChild(a),r.push(a)}var s=0,o=!1;function l(){if(e.alwaysDisplay||i.video.videoWidth/i.video.videoHeight!=t.clientWidth/t.clientHeight){r[s].removeAttribute("data-front"),++s>=r.length&&(s=0);var n=r[s],a=n._context;n.width=i.video.videoWidth,n.height=i.video.videoHeight,a.drawImage(i.video,0,0),n.setAttribute("data-front","")}i.player.api.paused?o=!1:i.timers.start((function(){l()}),1e3*e.delay)}return MistUtil.event.addListener(i.video,"playing",(function(){o||(l(),o=!0)})),t},subtitles:function(e){if(!("WebSocket"in window))return!1;var t=this;if(!("player"in t)||!("api"in t.player)||!("currentTime"in t.player.api))return!1;if(!("metaTrackSubscriptions"in t))return!1;var i=document.createElement("div"),r=document.createElement("span");i.appendChild(r);var n=document.createTextNode("");r.appendChild(n);var a=!1;function s(e){n.nodeValue=e.data.replace(/\<\/?[bui]\>/gi,"").replace(/{\/?[bui]}/gi,"").replace(/{\\a\d+}/gi,"").replace(/\<\/?font[^>]*?\>/gi,""),a&&(t.timers.stop(a),a=null),function i(r){a=t.timers.start((function(){if(t.player.api.paused)var r=MistUtil.event.addListener(t.video,"playing",(function(){i(e.time+("duration"in e?e.duration:5e3)-1e3*t.player.api.currentTime),MistUtil.event.removeListener(r)}));else n.nodeValue=""}),r)}("duration"in e?e.duration:5e3)}if(MistUtil.event.addListener(t.video,"seeked",(function(){n.nodeValue="",a&&t.timers.stop(a),a=null})),!("setWSSubtitle"in t.player.api)){var o=!1;t.player.api.setWSSubtitle=function(e){e!=o&&(void 0!==e&&t.metaTrackSubscriptions.add(e,s),e!=o&&t.metaTrackSubscriptions.remove(o,s),o="undefined"!=e&&e)}}return i},chromecast:function(){var e=this;if(!(!"".indexOf||e.options.host.indexOf("localhost")>-1||e.options.host.indexOf("::1")>-1)){var t,i,r,n,a=document.createElement("div"),s=document.createElement("div");s.className="mistvideo-casting";var o={},l={currentTime:!1,paused:!0,volume:1,muted:!1,buffer:[],loop:e.player.api?e.player.api.loop:e.options.loop};if(e.casting=!1,window.chrome&&window.chrome.cast||window.loadedCastApi)"loading"==window.loadedCastApi?(e.log("Not appending chromecast script - still loading"),e.timers.start((function(){d()}),200)):(e.log("Not appending chromecast script - already loaded"),d());else{window.__onGCastApiAvailable=function(t,i){t||e.log("Error while loading chromecast API: "+i),d()},window.loadedCastApi="loading";var c=document.createElement("script");c.setAttribute("src","//www.gstatic.com/cv/js/sender/v1/cast_sender.js?loadCastFramework=1"),document.head.appendChild(c),e.log("Appending chromecast script")}return a}function d(c){if(!window.chrome||!window.chrome.cast||!window.chrome.cast.isAvailable||c>5)return a.parentNode&&a.parentNode.removeChild(a),e.log("Chromecast is not supported"),void console.warn(chrome,chrome.cast,chrome.cast?chrome.cast.isAvailable:void 0,cast);if(!window.cast)return c||(c=0),e.log("Casting api loaded but cast function not yet available, retrying.."),void e.timers.start((function(){d(c++)}),200);e.log("Chromecast API loaded"),window.loadedCastApi&&"loading"!=window.loadedCastApi||(window.loadedCastApi=!0);var u=document.createElement("google-cast-launcher");function p(a){MistUtil.class.remove(u,"active"),MistUtil.class.remove(e.container,"casting"),s.parentNode&&s.parentNode.removeChild(s),!a&&cast.framework.CastContext.getInstance().getCurrentSession()&&cast.framework.CastContext.getInstance().getCurrentSession().endSession(!0),t?(e.player.api=t,t.currentTime=l.currentTime,t.play(),e.reload=i,e.nextCombo=r,e.unload=n):e.player.api.play(),e.player.api&&e.player.api.setTracks&&MistUtil.object.keys(o).length&&e.player.api.setTracks(o),e.casting=!1,e.log("Detached chromecast session")}a.appendChild(u),cast.framework.CastContext.getInstance().setOptions({receiverApplicationId:"E5F1558C",autoJoinPolicy:chrome.cast.AutoJoinPolicy.ORIGIN_SCOPED}),e.detachFromCast=p,u.addEventListener("click",(function(a){if(a.stopPropagation(),MistUtil.class.has(u,"active"))p();else{function c(){cast.framework.CastContext.getInstance().getCurrentSession().addMessageListener("urn:x-cast:mistcaster",(function(t,i){if(e.destroyed&&p(),(i=JSON.parse(i)).type)switch(i.type){case"log":case"error":e.log("[Chromecast] "+i.message,i.type);break;case"showError":e.showError.apply(e,i.args);break;case"event":switch(i.event){case"timeupdate":l.currentTime=i.currentTime,MistUtil.event.send(i.event,"chromecast",e.video);break;case"progress":l.buffer=i.buffer,MistUtil.event.send(i.event,"chromecast",e.video);break;case"pause":case"paused":case"ended":case"play":case"playing":l.paused=i.paused,MistUtil.event.send(i.event,"chromecast",e.video);break;case"volumechange":l.volume=i.volume,l.muted=i.muted,MistUtil.event.send(i.event,"chromecast",e.video);break;default:MistUtil.event.send(i.event,"chromecast",e.video)}break;case"detach":i.n==e.n&&p(!0);break;default:console.log("Unknown chromecast message type",i)}}));var a={type:"load",n:e.n,options:{host:e.options.host,loop:e.options.loop,poster:e.options.poster,streaminfo:e.options.streaminfo,urlappend:e.options.urlappend,forcePriority:e.options.forcePriority,setTracks:e.options.setTracks,controls:!1,skin:"default"},stream:e.stream};e.info&&"live"!=e.info.type&&(a.time=e.player.api.currentTime),"dev"==e.options.skin&&(a.options.skin=e.options.skin),e.player&&e.player.api&&(a.volume=e.player.api.volume,a.muted=e.player.api.muted,a.options.loop=e.player.api.loop),MistCast.send(a),t=e.player.api,i=e.reload,r=e.nextCombo,n=e.unload,o=e.options.setTracks?e.options.setTracks:{},e.player.api=new Proxy(t,{get:function(e,t,i){var r=e[t];switch(t){case"muted":case"volume":case"currentTime":case"paused":case"loop":return l[t];case"buffered":return new function(){this.length=l.buffer.length,this.start=function(e){return l.buffer[e][0]},this.end=function(e){return l.buffer[e][1]}};case"setTracks":case"play":case"pause":return function(){for(var e=[],i=0;i=t.scrollHeight-5})),r.logs)o(r.logs[l].time,r.logs[l].message,r.logs[l].data);return MistUtil.event.addListener(r.options.target,"log",(function(e){if(e.message){var t={};r.player&&r.player.api&&"currentTime"in r.player.api&&(t.currentTime=r.player.api.currentTime),o(new Date,e.message,t)}}),e),MistUtil.event.addListener(r.options.target,"error",(function(e){if(e.message){var t={type:"error"};r.player&&r.player.api&&"currentTime"in r.player.api&&(t.currentTime=r.player.api.currentTime),o(new Date,e.message,t)}}),e),e},decodingIssues:function(){if(this.player){var e=this,t=document.createElement("div");if(e.player.api){var i={"Playback score":function(){if("monitor"in e){if("vars"in e.monitor&&"score"in e.monitor.vars&&e.monitor.vars.values.length){var t=e.monitor.vars.values[e.monitor.vars.values.length-1];if("score"in t){Math.min(1,Math.max(0,t.score));return{x:t.clock,y:Math.min(1,Math.max(0,t.score)),options:{y:{min:0,max:1},x:{count:10}},val:Math.round(100*Math.min(1,Math.max(0,e.monitor.vars.score)))+"%"}}}return 0}},"Corrupted frames":function(){if(e.player.api&&"getVideoPlaybackQuality"in e.player.api){var t=e.player.api.getVideoPlaybackQuality();if(t)return t.corruptedVideoFrames?{val:MistUtil.format.number(t.corruptedVideoFrames),x:.001*(new Date).getTime(),y:t.corruptedVideoFrames,options:{x:{count:10}}}:0}},"Dropped frames":function(){if(e.player.api){if("getVideoPlaybackQuality"in e.player.api){var t=e.player.api.getVideoPlaybackQuality();if(t)return t.droppedVideoFrames?MistUtil.format.number(t.droppedVideoFrames):0}if("webkitDroppedFrameCount"in e.player.api)return e.player.api.webkitDroppedFrameCount}},"Total frames":function(){if(e.player.api&&"getVideoPlaybackQuality"in e.player.api){var t=e.player.api.getVideoPlaybackQuality();if(t)return MistUtil.format.number(t.totalVideoFrames)}},"Decoded audio":function(){if(e.player.api)return MistUtil.format.bytes(e.player.api.webkitAudioDecodedByteCount)},"Decoded video":function(){if(e.player.api)return MistUtil.format.bytes(e.player.api.webkitVideoDecodedByteCount)},"Negative acknowledgements":function(){if(e.player.api)return MistUtil.format.number(e.player.api.nackCount)},"Picture losses":function(){return MistUtil.format.number(e.player.api.pliCount)},"Packets lost":function(){return MistUtil.format.number(e.player.api.packetsLost)},"Packets received":function(){return MistUtil.format.number(e.player.api.packetsReceived)},"Bytes received":function(){if(e.player.api)return MistUtil.format.bytes(e.player.api.bytesReceived)},"Local latency [ms]":function(){if(e.player.api&&"getLatency"in e.player.api){var t=e.player.api.getLatency();return t?new Promise((function(e,i){t.then((function(t){var i=[];for(var r in t)t[r]&&i.push(r[0]+":"+Math.round(1e3*t[r]));i.length?e(i.join(" ")):e()}),i)})):new Promise((function(e,t){e()}),(function(){}))}},"Current bitrate":function(){var t;return e.player.monitor&&"currentBps"in e.player.monitor?(t=MistUtil.format.bits(e.player.monitor.currentBps))?t+"ps":t:e.player.api&&"currentBps"in e.player.api?(t=MistUtil.format.bits(e.player.api.currentBps()))?t+"ps":t:void 0},"Framerate in":function(){if(e.player.api&&"framerate_in"in e.player.api)return MistUtil.format.number(e.player.api.framerate_in())},"Framerate out":function(){if(e.player.api&&"framerate_out"in e.player.api)return MistUtil.format.number(e.player.api.framerate_out())}},r=[];for(var n in i)void 0!==i[n]()&&a({name:n,function:i[n]});t.update=function(){for(var i in r)r[i]();e.timers.start((function(){t.update()}),1e3)},t.update()}return t}function a(e){var i=document.createElement("label");t.appendChild(i),i.style.display="none";var n=document.createElement("span");i.appendChild(n),n.appendChild(document.createTextNode(e.name+":")),n.className="mistvideo-description";var a=document.createElement("span");i.appendChild(a);var s=document.createTextNode(e.value?e.value:"");a.appendChild(s);var o=document.createElement("span");a.appendChild(o),i.set=function(e){if(0!==e&&(this.style.display=""),"object"==typeof e){try{if(e instanceof Promise)return void e.then((function(e){i.set(e)}),(function(){}))}catch(e){}if("val"in e&&(s.nodeValue=e.val,a.className="value"),o.children.length)return(t=o.children[0]).addData(e);var t=MistUtil.createGraph({x:[e.x],y:[e.y]},e.options);return o.style.display="",MistUtil.empty(o),o.appendChild(t)}return s.nodeValue=e},t.appendChild(i),r.push((function(){var t=e.function();i.set(t)}))}},forcePlayer:function(){var e=document.createElement("label");e.title="Reload MistVideo and use the selected player";var t=this,i=document.createElement("span");e.appendChild(i),i.appendChild(document.createTextNode("Force player: "));var r=document.createElement("select");e.appendChild(r);var n=document.createElement("option");for(var a in r.appendChild(n),n.value="",n.appendChild(document.createTextNode("Automatic")),mistplayers){n=document.createElement("option");r.appendChild(n),n.value=a,n.appendChild(document.createTextNode(mistplayers[a].name))}return this.options.forcePlayer&&(r.value=this.options.forcePlayer),MistUtil.event.addListener(r,"change",(function(){t.options.forcePlayer=""!=this.value&&this.value,t.options.forcePlayer!=t.playerName&&t.reload("Reloading to force player.")})),e},forceType:function(){if(this.info){var e=document.createElement("label");e.title="Reload MistVideo and use the selected protocol";var t=this,i=document.createElement("span");e.appendChild(i),i.appendChild(document.createTextNode("Force protocol: "));var r=document.createElement("select");e.appendChild(r);var n=document.createElement("option");r.appendChild(n),n.value="",n.appendChild(document.createTextNode("Automatic"));var a={};for(var s in t.info.source){var o=t.info.source[s];if(!(o.type in a)){a[o.type]=1;n=document.createElement("option");r.appendChild(n),n.value=o.type,n.appendChild(document.createTextNode(MistUtil.format.mime2human(o.type)))}}return this.options.forceType&&(r.value=this.options.forceType),MistUtil.event.addListener(r,"change",(function(){t.options.forceType=""!=this.value&&this.value,t.source&&t.options.forceType==t.source.type||t.reload("Reloading to force new type.")})),e}},forceSource:function(){var e=document.createElement("label");e.title="Reload MistVideo and use the selected source";var t=this,i=document.createElement("span");e.appendChild(i),i.appendChild(document.createTextNode("Force source: "));var r=document.createElement("select");e.appendChild(r);var n=document.createElement("option");for(var a in r.appendChild(n),n.value="",n.appendChild(document.createTextNode("Automatic")),t.info.source){var s=t.info.source[a];n=document.createElement("option");r.appendChild(n),n.value=a,n.appendChild(document.createTextNode(s.url+" ("+MistUtil.format.mime2human(s.type)+")"))}return this.options.forceSource&&(r.value=this.options.forceSource),MistUtil.event.addListener(r,"change",(function(){t.options.forceSource=""!=this.value&&this.value,t.options.forceSource!=t.source.index&&t.reload("Reloading to force new source.")})),e}}},MistSkins.dev.css={skin:misthost+"/skins/dev.css"},MistSkins.dev.structure.submenu=MistUtil.object.extend({},MistSkins.default.structure.submenu,!0),MistSkins.dev.structure.submenu.type="draggable",MistSkins.dev.structure.submenu.style.width="25em",MistSkins.dev.structure.submenu.children.unshift({type:"container",style:{flexShrink:1},classes:["mistvideo-column"],children:[{if:function(){return this.playerName&&this.source},then:{type:"container",classes:["mistvideo-description","mistvideo-displayCombo"],style:{display:"block"},children:[{type:"playername",style:{display:"inline"}},{type:"text",text:"is playing",style:{margin:"0 0.2em"}},{type:"mimetype"}]}},{type:"log"},{type:"decodingIssues"},{type:"container",classes:["mistvideo-column","mistvideo-devcontrols"],style:{"font-size":"0.9em"},children:[{type:"text",text:"Player control"},{type:"container",classes:["mistvideo-devbuttons"],style:{"flex-wrap":"wrap"},children:[{type:"button",title:"Build MistVideo again",label:"MistVideo.reload();",onclick:function(){this.reload("Dev-reload button clicked.")}},{type:"button",title:"Switch to the next available player and source combination",label:"MistVideo.nextCombo();",onclick:function(){this.nextCombo()}}]},{type:"forcePlayer"},{type:"forceType"}]}]});var mistplayers={};function MistPlayer(){}function mistPlay(e,t){return new MistVideo(e,t)}function MistVideo(e,t){var i=this;function r(e){if("meta"in e&&"tracks"in e.meta){var t=e.meta.tracks;for(var i in t)if("video"==t[i].type)return!0}return!1}function n(e){if(i.player&&i.player.api&&i.player.api.unload&&(i.log("Received new stream info while a player was already loaded: unloading player"),i.player.api.unload()),i.info=e,i.info.updated=new Date,MistUtil.event.send("haveStreamInfo",e,i.options.target),i.log("Stream info was loaded succesfully."),"error"in e){var n=e.error;return"on_error"in e?(i.log(n),n=e.on_error):"perc"in e&&(n+=" ("+Math.round(10*e.perc)/10+"%)"),void i.showError(n,{reload:!0,hideTitle:!0})}if(i.calcSize=function(e){e||(e={width:!1,height:!1});var r=e.width||!(!("width"in t)||!t.width)&&t.width,n=e.height||!(!("height"in t)||!t.height)&&t.height;if(this.info&&"source"in this.info)if(this.info.hasVideo&&"audio"!=this.source.type.split("/")[1]){if(!r||!n){var a=i.info.width/i.info.height;if(r||n)r?n=r/a:r=n*a;else{var s="maxwidth"in t&&t.maxwidth?t.maxwidth:window.innerWidth,o="maxheight"in t&&t.maxheight?t.maxheight:window.innerHeight;r=i.info.width,n=i.info.height;function l(e){r/=e,n/=e}r<426&&l(r/426),n<240&&l(n/240),s&&r>s&&l(r/s),o&&n>o&&l(n/o)}}}else r||(r=480),n||(n=42);else r=640,n=480;return this.size={width:Math.round(r),height:Math.round(n)},this.size},e.hasVideo=r(e),"live"==e.type){var a=0;for(var s in i.info.meta.tracks)a=Math.max(a,i.info.meta.tracks[s].lastms);e.lastms=a}else{var o=i.resumeTime;if(o){var l=function(){i.player&&i.player.api&&(i.player.api.currentTime=o),this.removeEventListener("initialized",l)};MistUtil.event.addListener(i.options.target,"initialized",l)}}i.options.ABR_bitrate&&i.options.ABR_resize&&i.info&&!i.info.selver&&(i.options.ABR_bitrate=!1),i.choosePlayer()?(i.reporting&&i.reporting.report({player:i.playerName,sourceType:i.source.type,sourceUrl:i.source.url,pageUrl:location.href}),i.player=new mistplayers[i.playerName].player,i.player.onreadylist=[],i.player.onready=function(e){this.onreadylist.push(e)},i.player.build(i,(function(e){if(i.log("Building new player"),i.container.removeAttribute("data-loading"),i.video=e,i.reporting&&i.reporting.init(),"api"in i.player){i.monitor={MistVideo:i,delay:1,averagingSteps:20,threshold:function(){return"webrtc"==this.MistVideo.source.type?.95:.75},init:function(){if(!this.vars||!this.vars.active){this.MistVideo.log("Enabling monitor"),this.vars={values:[],score:!1,active:!0};var e=this;!function t(){e.vars&&e.vars.active&&(e.vars.timer=e.MistVideo.timers.start((function(){var i=e.calcScore();!1!==i&&e.check(i)&&e.action(),t()}),1e3*e.delay))}()}},destroy:function(){this.vars&&this.vars.active&&(this.MistVideo.log("Disabling monitor"),this.MistVideo.timers.stop(this.vars.timer),delete this.vars)},reset:function(){this.vars&&this.vars.active?(this.MistVideo.log("Resetting monitor"),this.vars.values=[]):this.init()},calcScore:function(){var e=this.vars.values;if(e.push(this.getValue()),e.length<=1)return!1;var t=this.valueToScore(e[0],e[e.length-1]);return e.length>this.averagingSteps&&e.shift(),t=Math.max(t,e[e.length-1].score),this.vars.score=t,i.reporting&&i.reporting.stats.set("playbackScore",Math.round(10*t)/10),t},valueToScore:function(e,t){var i=1;return"player"in this.MistVideo&&"api"in this.MistVideo.player&&"playbackRate"in this.MistVideo.player.api&&(i=this.MistVideo.player.api.playbackRate),(t.video-e.video)/(t.clock-e.clock)/i},getValue:function(){var e={clock:.001*(new Date).getTime(),video:this.MistVideo.player.api.currentTime};return this.vars.values.length&&(e.score=this.valueToScore(this.vars.values[this.vars.values.length-1],e)),e},check:function(e){return!(this.vars.values.length<.5*this.averagingSteps)&&(e=e.socket.CLOSING&&e.init(),this.send_queue.push(t)};var t=!1;if(e.socket.setTracks=function(){e.s({type:"tracks",meta:MistUtil.object.keys(e.subscriptions).join(",")})},e.socket.onopen=function(){for(i.log("Metadata socket opened"),e.socket.setTracks(),1!=i.player.api.playbackRate&&e.s({type:"set_speed",play_rate:i.player.api.playbackRate}),e.s({type:"seek",seek_time:Math.round(1e3*i.player.api.currentTime),ff_to:Math.round(1e3*(i.player.api.currentTime+5))}),e.socket.addEventListener("message",(function(r){if(r.data){var n=JSON.parse(r.data);if(n){if("time"in n&&"track"in n&&"data"in n){var a=!1;if("all"in e.subscriptions&&(e.subscriptions.all.buffer.push(n),a=!0),n.track in e.subscriptions&&(e.subscriptions[n.track].buffer.push(n),a=!0),a)if(e.checktimer){var s=i.timers.list[e.checktimer];if(s)s>(new Date).getTime()+n.time-1e3*i.player.api.currentTime&&(i.log("The metadata socket received a message that should be displayed sooner than the current check time; resetting"),i.timers.stop(e.checktimer),e.checktimer=null,e.check())}else e.check()}if("type"in n)switch(n.type){case"on_time":!t&&n.data.current>1e3*(i.player.api.currentTime+30)&&(t=!0,e.s({type:"hold"}),i.log("Pausing metadata buffer because it is very far ahead, checking again in 5 seconds: "+n.data.current+" > "+1e3*i.player.api.currentTime),i.timers.start((function(){i.player.api.paused||e.s({type:"play"}),e.s({type:"fast_forward",ff_to:Math.round(1e3*(i.player.api.currentTime+5))})}),5e3));break;case"seek":for(var o in e.subscriptions)e.subscriptions[o].buffer=[];i.log("Cleared metadata buffer after completed seek"),e.checktimer&&(i.timers.stop(e.checktimer),e.checktimer=null)}}else i.log("Subtitle websocket received invalid message.")}else i.log("Subtitle websocket received empty message.")})),e.socket.onclose=function(){i.log("Metadata socket closed")};e.send_queue.length&&e.socket.readyState==e.socket.OPEN;)e.s(e.send_queue.shift())},!("seeked"in this.listeners)){var r=(new Date).getTime();e.check=function(){if(e.checktimer&&(i.timers.stop(e.checktimer),e.checktimer=null),!i.player.api.paused){var t=null;for(var n in e.subscriptions){for(var a=e.subscriptions[n].buffer;a.length&&a[0].time<=1e3*i.player.api.currentTime;){var s=a.shift();if(!(s.time<1e3*(i.player.api.currentTime-5)))for(var o in e.subscriptions[n].callbacks)e.subscriptions[n].callbacks[o].call(i,s)}a.length&&(t=Math.min(null===t?1e9:t,a[0].time))}var l=(new Date).getTime();if(l>r+5e3&&(e.s({type:"fast_forward",ff_to:Math.round(1e3*(i.player.api.currentTime+5))}),r=l),t){var c=t-1e3*i.player.api.currentTime;e.checktimer=i.timers.start((function(){e.check()}),c)}}},this.listeners.seeked=MistUtil.event.addListener(i.video,"seeked",(function(){for(var t in e.subscriptions)e.subscriptions[t].buffer=[];e.s({type:"seek",seek_time:Math.round(1e3*i.player.api.currentTime),ff_to:Math.round(1e3*(i.player.api.currentTime+5))}),r=(new Date).getTime()})),this.listeners.pause=MistUtil.event.addListener(i.video,"pause",(function(){e.s({type:"hold"}),i.timers.stop(e.checktimer),e.checktimer=null})),this.listeners.playing=MistUtil.event.addListener(i.video,"playing",(function(){e.s({type:"play"}),e.checktimer||e.check()})),this.listeners.ratechange=MistUtil.event.addListener(i.video,"ratechange",(function(){e.s({type:"set_speed",play_rate:i.player.api.playbackRate})}))}e.socket.readyState==e.socket.OPEN&&e.socket.onopen()},destroy:function(){for(var e in i.log("Closing metadata socket.."),this.socket.close(),this.socket=null,this.subscriptions={},this.listeners)MistUtil.event.removeListener(this.listeners[e]);this.listeners={}},add:function(e,t){"function"!=typeof e||t||(t=e,e="all"),"function"==typeof t&&(e in this.subscriptions||(this.subscriptions[e]={buffer:[],callbacks:[]}),this.subscriptions[e].callbacks.push(t),null===this.socket?this.init():this.socket.setTracks())},remove:function(e,t){if(e in this.subscriptions){for(var i in this.subscriptions[e].callbacks)if(t==this.subscriptions[e].callbacks[i]){this.subscriptions[e].callbacks.splice(i,1);break}0==this.subscriptions[e].callbacks.length&&(delete this.subscriptions[e],MistUtil.object.keys(this.subscriptions).length?this.socket.setTracks():this.destroy())}}},"function"==typeof t.subscribeToMetaTrack&&(t.subscribeToMetaTrack=[["all",t.subscribeToMetaTrack]]),t.subscribeToMetaTrack.length))for(var n in"object"!=typeof t.subscribeToMetaTrack[0]&&(t.subscribeToMetaTrack=[t.subscribeToMetaTrack]),t.subscribeToMetaTrack)i.metaTrackSubscriptions.add.apply(i.metaTrackSubscriptions,t.subscribeToMetaTrack[n])}}MistUtil.empty(i.options.target),new MistSkin(i),i.container=new MistUI(i),i.options.target.appendChild(i.container),i.container.setAttribute("data-loading",""),i.video.p=i.player;r=["abort","canplay","canplaythrough",,"emptied","ended","loadeddata","loadedmetadata","loadstart","pause","play","playing","ratechange","seeked","seeking","stalled","volumechange","waiting","metaUpdate_tracks","resizing"];for(var n in r)MistUtil.event.addListener(i.video,r[n],(function(e){e.message&&"chromecast"==e.message||i.log("Player event fired: "+e.type)}));if(MistUtil.event.addListener(i.video,"error",(function(e){var t;if("player"in i&&"api"in i.player&&"error"in i.player.api&&i.player.api.error)if("message"in i.player.api.error)t=i.player.api.error.message;else if("code"in i.player.api.error&&i.player.api.error instanceof MediaError){var r={1:"MEDIA_ERR_ABORTED: The fetching of the associated resource was aborted by the user's request.",2:"MEDIA_ERR_NETWORK: Some kind of network error occurred which prevented the media from being successfully fetched, despite having previously been available.",3:"MEDIA_ERR_DECODE: Despite having previously been determined to be usable, an error occurred while trying to decode the media resource, resulting in an error.",4:"MEDIA_ERR_SRC_NOT_SUPPORTED: The associated resource or media provider object (such as a MediaStream) has been found to be unsuitable."};t=i.player.api.error.code in r?r[i.player.api.error.code]:"MediaError code "+i.player.api.error.code}else"string"!=typeof(t=i.player.api.error)&&(t=JSON.stringify(t));else t="An error was encountered.";"Stream is online"==i.state?i.showError(t):(i.log(t,"error"),i.showError(i.state,{polling:!0}))})),"setSize"in i.player&&(i.player.videocontainer=i.video.parentNode,i.video.currentTarget=i.options.target,MistUtil.class.has(i.options.target,"mistvideo-secondaryVideo")||(i.player.resizeAll=function(){function e(t,i){if(t.video.currentTarget==i)return t.video;if(t.secondary)for(var r=0;r3600&&(t.reloadDelay/=1e3,this.log("A reloadDelay of more than an hour was set: assuming milliseconds were intended. ReloadDelay is now "+t.reloadDelay+"s")),new MistSkin(this),this.checkCombo=function(e,t){e||(e={}),e=MistUtil.object.extend(MistUtil.object.extend({},this.options),e);var r,n,a=!1;for(var s in e.forceSource?(r=[i.info.source[e.forceSource]],i.log("Forcing source "+e.forceSource+": "+r[0].type+" @ "+r[0].url)):e.forceType?(r=i.info.source.filter((function(t){return t.type==e.forceType})),i.log("Forcing type "+e.forceType)):r=i.info.source,mistplayers)mistplayers[s].shortname=s;e.forcePlayer&&mistplayers[e.forcePlayer]?(n=[mistplayers[e.forcePlayer]],i.log("Forcing player "+e.forcePlayer)):n=MistUtil.object.values(mistplayers),r=[].concat(r);var o={first:"source",source:[function(e){return"origIndex"in e||(e.origIndex=i.info.source.indexOf(e)),e.origIndex}],player:[{priority:1}]},l={inner:"player",outer:"source"};if(e.forcePriority){if("source"in e.forcePriority){if(!(e.forcePriority.source instanceof Array))throw"forcePriority.source is not an array.";o.source=e.forcePriority.source.concat(o.source),MistUtil.array.multiSort(r,o.source)}if("player"in e.forcePriority){if(!(e.forcePriority.player instanceof Array))throw"forcePriority.player is not an array.";o.player=e.forcePriority.player.concat(o.player),MistUtil.array.multiSort(n,o.player)}"first"in e.forcePriority&&(o.first=e.forcePriority.first),"player"==o.first&&(l.outer="player",l.inner="source")}var c={player:{list:n,current:!1},source:{list:r,current:!1}};if(e.startCombo){e.startCombo.started={player:!1,source:!1};for(s=0;s=2))for(var v in c[l.inner].list)if(c[l.inner].current=v,!(d(l.inner)>=1)){a=c.source.list[c.source.current];var y=c.player.list[c.player.current].shortname,g=mistplayers[y];if(g.isMimeSupported(a.type)){var b=g.isBrowserSupported(a.type,a,i);if(b){var k=p(b);if(k>u.score&&(t||i.log("Found a "+(u.score?"better":"working")+" combo: "+g.name+" with "+a.url+" (Score: "+k+")"),(u={score:k,player:y,source:a,source_index:c.source.current}).score==m))return u}}}return!!u.score&&u},this.choosePlayer=function(){i.log("Checking available players..");var e=this.checkCombo();if(!e)return!1;var t=mistplayers[e.player],r=e.source;return i.log("Selected: "+t.name+" with "+r.type+" @ "+r.url),i.playerName=e.player,(r=MistUtil.object.extend({},r)).index=e.source_index,r.url=i.urlappend(r.url),i.source=r,MistUtil.event.send("comboChosen","Player/source combination selected",i.options.target),!0},i.calcSize=function(){return{width:640,height:480}},MistUtil.empty(i.options.target),new MistSkin(i),i.container=new MistUI(i,i.skin.structure.placeholder),i.options.target.appendChild(i.container),i.container.setAttribute("data-loading",""),"WebSocket"in window){!function e(){i.log("Opening stream status stream through websocket..");var t,s=i.options.host.replace(/^http/i,"ws");s=MistUtil.http.url.addParam(i.urlappend(s+"/json_"+encodeURIComponent(i.stream)+".js"),{metaeverywhere:1,inclzero:1});try{t=new WebSocket(s)}catch(e){return i.log("Error while attempting to open WebSocket to "+s),void a()}i.socket=t,t.die=!1,t.destroy=function(){this.die=!0,i.reporting&&(i.reporting.reportStats(),i.reporting=!1),this.onclose=function(){},this.close()},t.timeOut=i.timers.start((function(){t.readyState<=1&&(t.destroy(),a())}),5e3),t.onopen=function(e){this.wasConnected=!0,i.reporting||(i.reporting={stats:{set:function(e,t){this.d[e]=t},add:function(e,t){void 0===t&&(t=1),this.d[e]+=t},d:{nWaiting:0,timeWaiting:0,nStalled:0,timeStalled:0,timeUnpaused:0,nError:0,nLog:0,videoHeight:null,videoWidth:null,playerHeight:null,playerWidth:null},last:{firstPlayback:null,nWaiting:0,timeWaiting:0,nStalled:0,timeStalled:0,timeUnpaused:0,nError:0,lastError:null,playbackScore:1,nLog:0,autoplay:null,videoHeight:null,videoWidth:null,playerHeight:null,playerWidth:null}},report:function(e){1==i.socket.readyState&&i.socket.send(JSON.stringify(e))},reportStats:function(){var e={},t=!1,r=i.logs.slice(this.stats.last.nLog);for(var n in this.stats.d)this.stats.d[n]!=this.stats.last[n]&&(e[n]=this.stats.d[n],this.stats.last[n]=e[n],t=!0);if(t){if(r.length)for(var n in e.logs=[],r)e.logs.push(r[n].message);this.report(e)}i.timers.start((function(){i.reporting&&i.reporting.reportStats()}),5e3)},init:function(){var e=i.video,t=MistUtil.event.addListener(e,"playing",(function(){i.reporting.stats.set("firstPlayback",(new Date).getTime()-i.bootMs),MistUtil.event.removeListener(t)}));if(MistUtil.event.addListener(e,"waiting",(function(){i&&i.reporting&&i.reporting.stats.add("nWaiting")})),MistUtil.event.addListener(e,"stalled",(function(){i&&i.reporting&&i.reporting.stats.add("nStalled")})),MistUtil.event.addListener(i.options.target,"error",(function(e){i&&i.reporting&&(i.reporting.stats.add("nError"),i.reporting.stats.set("lastError",e.message))}),e),Object&&Object.defineProperty){var r=0,n=!1,a=0,s=!1,o=0,l=!1,c=i.reporting.stats.d;Object.defineProperty(c,"timeWaiting",{get:function(){return r+(n?(new Date).getTime()-n:0)}}),Object.defineProperty(c,"timeStalled",{get:function(){return a+(s?(new Date).getTime()-s:0)}}),Object.defineProperty(c,"timeUnpaused",{get:function(){return o+(l?(new Date).getTime()-l:0)}}),Object.defineProperty(c,"nLog",{get:function(){return i.logs.length}}),Object.defineProperty(c,"videoHeight",{get:function(){return i.video?i.video.videoHeight:null}}),Object.defineProperty(c,"videoWidth",{get:function(){return i.video?i.video.videoWidth:null}}),Object.defineProperty(c,"playerHeight",{get:function(){return i.video?i.video.clientHeight:null}}),Object.defineProperty(c,"playerWidth",{get:function(){return i.video?i.video.clientWidth:null}}),MistUtil.event.addListener(e,"waiting",(function(){r=c.timeWaiting,n=(new Date).getTime()})),MistUtil.event.addListener(e,"stalled",(function(){a=c.timeStalled,s=(new Date).getTime()}));var d=["playing","pause"];for(var u in d)MistUtil.event.addListener(e,d[u],(function(){r=c.timeWaiting,a=c.timeStalled,n=!1,s=!1}));MistUtil.event.addListener(e,"playing",(function(){o=c.timeUnpaused,l=(new Date).getTime()})),MistUtil.event.addListener(e,"pause",(function(){o=c.timeUnpaused,l=!1}))}this.reportStats()}})},t.onclose=function(t){if(!this.die)return this.wasConnected?(i.log("Reopening websocket.."),void e()):void a()};var o=!1;t.addEventListener("message",(function(e){t.timeOut&&(i.timers.stop(t.timeOut),t.timeOut=!1);var a=JSON.parse(e.data);if(a||i.showError("Error while parsing stream status stream. Obtained: "+e.data.toString(),{reload:!0}),"error"in a){var s;e=a.error;switch("on_error"in a?(i.log(e),e=a.on_error):"perc"in a&&(e+=" ("+Math.round(10*a.perc)/10+"%)"),i.state=a.error,a.error){case"Stream is offline":i.info=!1,i.player&&i.player.api&&i.player.api.currentTime&&(i.resumeTime=i.player.api.currentTime);case"Stream is initializing":case"Stream is booting":case"Stream is waiting for data":case"Stream is shutting down":case"Stream status is invalid?!":if(i.player&&i.player.api&&!i.player.api.paused)return i.log(a.error,"error"),o||(o=MistUtil.event.addListener(i.video,"ended",(function(){i.showError(a.error,{polling:!0})}))),void(o=MistUtil.event.addListener(i.video,"waiting",(function(){i.showError(a.error,{polling:!0})})));s={polling:!0};break;default:s={reload:!0}}i.showError(e,s)}else{if(i.state="Stream is online",i.clearError(),o&&MistUtil.event.removeListener(o),!i.info)return void n(a);var l=function e(t,i){if(t==i)return!1;if("object"==typeof t&&void 0!==i){var r={};for(var n in t)if(!(MistUtil.array.indexOf(["lastms","hasVideo"],n)>=0)){var a=e(t[n],i[n]);a&&(r[n]=!0===a?[t[n],i[n]]:a)}for(var n in i)MistUtil.array.indexOf(["lastms","hasVideo"],n)>=0||n in t||(r[n]=[t[n],i[n]]);return!!MistUtil.object.keys(r).length&&r}return!0}(a,i.info);if(l){if("source"in l&&"error"in i.info)return void i.reload("Reloading, stream info has error");i.info=MistUtil.object.extend(i.info,a),i.info.updated=new Date;var c=!1;for(var d in l)switch(d){case"meta":for(var u in l[d])if("tracks"===u)i.info.hasVideo=r(i.info),MistUtil.event.send("metaUpdate_tracks",a,i.video);break;case"width":case"height":c=!0}c&&i.player.resize()}else i.log("Metachange: no differences detected")}}))}()}else a();return this.unload=function(e){if(!this.destroyed){for(var t in this.log("Unloading.."),this.destroyed=!0,this.timers.stop("all"),this.errorListeners){var r=this.errorListeners[t];if(r.src in MistUtil.scripts.list){var n=MistUtil.array.indexOf(MistUtil.scripts.list[r.src].subscribers);n>=0&&MistUtil.scripts.list[r.src].subscribers.splice(n,1)}}if("monitor"in i&&"destroy"in i.monitor&&i.monitor.destroy(),this.socket&&(this.reporting&&(this.reporting.reportStats(),this.reporting.report({unload:e||null})),this.socket.destroy()),this.player&&this.player.api&&("pause"in this.player.api&&this.player.api.pause(),"setSource"in this.player.api&&this.player.api.setSource(""),"unload"in this.player.api))try{this.player.api.unload()}catch(a){i.log("Error while unloading player: "+a.message)}if(this.metaTrackSubscriptions&&this.metaTrackSubscriptions.socket&&this.metaTrackSubscriptions.destroy(),this.UI&&this.UI.elements)for(var t in this.UI.elements){var a=this.UI.elements[t];if("attachedListeners"in a)for(var t in a.attachedListeners)MistUtil.event.removeListener(a.attachedListeners[t]);a.parentNode&&a.parentNode.removeChild(a)}this.video&&MistUtil.empty(this.video),"container"in this&&(MistUtil.empty(this.container),delete this.container),MistUtil.empty(this.options.target),delete this.video}},this.reload=function(e){var t="player"in this&&"api"in this.player&&this.player.api.currentTime;this.unload(e);var r=mistPlay(this.stream,this.options);if(t&&"live"!=this.info.type){var n=function(){r.player&&r.player.api&&(r.player.api.currentTime=t),this.removeEventListener("initialized",n)};MistUtil.event.addListener(this.options.target,"initialized",n)}return i},this.nextCombo=function(){var e=!1;"player"in this&&"api"in this.player&&(e=this.player.api.currentTime);var t={source:this.source.index,player:this.playerName};if(!this.checkCombo({startCombo:t},!0)){if(!this.checkCombo({startCombo:!1},!0))return;t=!1}this.unload("nextCombo");var r=this.options;if(r.startCombo=t,i=mistPlay(this.stream,r),e&&isFinite(e)&&"live"!=this.info.type){var n=function(){"player"in i&&"api"in i.player&&(i.player.api.currentTime=e),this.removeEventListener("initialized",n)};MistUtil.event.addListener(r.target,"initialized",n)}},this.onPlayerBuilt=function(){},t.MistVideoObject&&(t.MistVideoObject.reference=this),this} \ No newline at end of file diff --git a/embed/min/wrappers/dashjs.js b/embed/min/wrappers/dashjs.js index 59281cf0..2c68ce73 100644 --- a/embed/min/wrappers/dashjs.js +++ b/embed/min/wrappers/dashjs.js @@ -1 +1 @@ -mistplayers.dashjs={name:"Dash.js player",mimes:["dash/video/mp4"],priority:MistUtil.object.keys(mistplayers).length+1,isMimeSupported:function(e){return MistUtil.array.indexOf(this.mimes,e)==-1?false:true},isBrowserSupported:function(e,t,i){if(location.protocol!=MistUtil.http.url.split(t.url).protocol){i.log("HTTP/HTTPS mismatch for this source");return false}if(location.protocol=="file:"){i.log("This source ("+e+") won't load if the page is run via file://");return false}if(!("MediaSource"in window)){return false}if(!MediaSource.isTypeSupported){return true}var r={};var a=false;for(var s in i.info.meta.tracks){if(i.info.meta.tracks[s].type=="meta"){if(i.info.meta.tracks[s].codec=="subtitle"){a=true}continue}if(!(i.info.meta.tracks[s].type in r)){r[i.info.meta.tracks[s].type]={}}r[i.info.meta.tracks[s].type][MistUtil.tracks.translateCodec(i.info.meta.tracks[s])]=1}var o=[];for(var n in r){var l=false;for(var f in r[n]){if(MediaSource.isTypeSupported('video/mp4;codecs="'+f+'"')){l=true;break}}if(l){o.push(n)}}if(a){for(var s in i.info.source){if(i.info.source[s].type=="html5/text/vtt"){o.push("subtitle");break}}}return o.length?o:false},player:function(){this.onreadylist=[]},scriptsrc:function(e){return e+"/dashjs.js"}};var p=mistplayers.dashjs.player;p.prototype=new MistPlayer;p.prototype.build=function(e,t){var i=this;this.onDashLoad=function(){if(e.destroyed){return}e.log("Building DashJS player..");var r=document.createElement("video");if("Proxy"in window){var a={get:{},set:{}};e.player.api=new Proxy(r,{get:function(e,t,i){if(t in a.get){return a.get[t].apply(e,arguments)}var r=e[t];if(typeof r==="function"){return function(){return r.apply(e,arguments)}}return r},set:function(e,t,i){if(t in a.set){return a.set[t].call(e,i)}return e[t]=i}});if(e.info.type=="live"){a.get.duration=function(){var t=0;if(this.buffered.length){t=this.buffered.end(this.buffered.length-1)}var i=((new Date).getTime()-e.player.api.lastProgress.getTime())*.001;return t+i+-1*e.player.api.liveOffset+45};a.set.currentTime=function(t){var i=t-e.player.api.duration;e.log("Seeking to "+MistUtil.format.time(t)+" ("+Math.round(i*-10)/10+"s from live)");e.video.currentTime=t};MistUtil.event.addListener(r,"progress",function(){e.player.api.lastProgress=new Date});e.player.api.lastProgress=new Date;e.player.api.liveOffset=0}}else{i.api=r}if(e.options.autoplay){r.setAttribute("autoplay","")}if(e.options.loop&&e.info.type!="live"){r.setAttribute("loop","")}if(e.options.poster){r.setAttribute("poster",e.options.poster)}if(e.options.muted){r.muted=true}if(e.options.controls=="stock"){r.setAttribute("controls","")}var s=dashjs.MediaPlayer().create();s.initialize(r,e.source.url,e.options.autoplay);i.dash=s;var o=["METRIC_ADDED","METRIC_UPDATED","METRIC_CHANGED","METRICS_CHANGED","FRAGMENT_LOADING_STARTED","FRAGMENT_LOADING_COMPLETED","LOG","PLAYBACK_TIME_UPDATED","PLAYBACK_PROGRESS"];for(var n in dashjs.MediaPlayer.events){if(o.indexOf(n)<0){i.dash.on(dashjs.MediaPlayer.events[n],function(t){e.log("Player event fired: "+t.type)})}}e.player.setSize=function(e){this.api.style.width=e.width+"px";this.api.style.height=e.height+"px"};e.player.api.setSource=function(t){e.player.dash.attachSource(t)};if(e.options.controls!="stock"){i.dash.updateSettings({streaming:{text:{defaultEnabled:false}}})}var l=false;i.dash.on("allTextTracksAdded",function(){l=true});e.player.api.setSubtitle=function(t){if(!l){var r=function(){e.player.api.setSubtitle(t);i.dash.off("allTextTracksAdded",r)};i.dash.on("allTextTracksAdded",r);return}if(!t){i.dash.enableText(false);return}var a=i.dash.getTracksFor("text");for(var s in a){var o="idx"in t?t.idx:t.trackid;if(a[s].id==o){i.dash.setTextTrack(s);if(!i.dash.isTextEnabled()){i.dash.enableText()}return true}}return false};MistUtil.event.addListener(r,"progress",function(t){if(e.container.getAttribute("data-loading")=="stalled"){e.container.removeAttribute("data-loading")}});i.api.unload=function(){i.dash.reset()};e.log("Built html");t(r)};if("dashjs"in window){this.onDashLoad()}else{var r=MistUtil.scripts.insert(e.urlappend(mistplayers.dashjs.scriptsrc(e.options.host)),{onerror:function(t){var i="Failed to load dashjs.js";if(t.message){i+=": "+t.message}e.showError(i)},onload:i.onDashLoad},e)}}; \ No newline at end of file +mistplayers.dashjs={name:"Dash.js player",mimes:["dash/video/mp4"],priority:MistUtil.object.keys(mistplayers).length+1,isMimeSupported:function(e){return MistUtil.array.indexOf(this.mimes,e)==-1?false:true},isBrowserSupported:function(e,t,i){if(location.protocol!=MistUtil.http.url.split(t.url).protocol){i.log("HTTP/HTTPS mismatch for this source");return false}if(location.protocol=="file:"){i.log("This source ("+e+") won't load if the page is run via file://");return false}if(!("MediaSource"in window)){return false}if(!MediaSource.isTypeSupported){return true}var r={};var a=false;for(var s in i.info.meta.tracks){if(i.info.meta.tracks[s].type=="meta"){if(i.info.meta.tracks[s].codec=="subtitle"){a=true}continue}if(!(i.info.meta.tracks[s].type in r)){r[i.info.meta.tracks[s].type]={}}r[i.info.meta.tracks[s].type][MistUtil.tracks.translateCodec(i.info.meta.tracks[s])]=1}var o=[];for(var n in r){var l=false;for(var f in r[n]){if(MediaSource.isTypeSupported('video/mp4;codecs="'+f+'"')){l=true;break}}if(l){o.push(n)}}if(a){for(var s in i.info.source){if(i.info.source[s].type=="html5/text/vtt"){o.push("subtitle");break}}}return o.length?o:false},player:function(){this.onreadylist=[]},scriptsrc:function(e){return e+"/dashjs.js"}};var p=mistplayers.dashjs.player;p.prototype=new MistPlayer;p.prototype.build=function(e,t){var i=this;this.onDashLoad=function(){if(e.destroyed){return}e.log("Building DashJS player..");var r=document.createElement("video");if("Proxy"in window){var a={get:{},set:{}};e.player.api=new Proxy(r,{get:function(e,t,i){if(t in a.get){return a.get[t].apply(e,arguments)}var r=e[t];if(typeof r==="function"){return function(){return r.apply(e,arguments)}}return r},set:function(e,t,i){if(t in a.set){return a.set[t].call(e,i)}return e[t]=i}});if(e.info.type=="live"){a.get.duration=function(){var t=0;if(this.buffered.length){t=this.buffered.end(this.buffered.length-1)}var i=((new Date).getTime()-e.player.api.lastProgress.getTime())*.001;return t+i+-1*e.player.api.liveOffset+45};a.set.currentTime=function(t){var i=t-e.player.api.duration;e.log("Seeking to "+MistUtil.format.time(t)+" ("+Math.round(i*-10)/10+"s from live)");e.video.currentTime=t};MistUtil.event.addListener(r,"progress",(function(){e.player.api.lastProgress=new Date}));e.player.api.lastProgress=new Date;e.player.api.liveOffset=0}}else{i.api=r}if(e.options.autoplay){r.setAttribute("autoplay","")}if(e.options.loop&&e.info.type!="live"){r.setAttribute("loop","")}if(e.options.poster){r.setAttribute("poster",e.options.poster)}if(e.options.muted){r.muted=true}if(e.options.controls=="stock"){r.setAttribute("controls","")}var s=dashjs.MediaPlayer().create();s.initialize(r,e.source.url,e.options.autoplay);i.dash=s;var o=["METRIC_ADDED","METRIC_UPDATED","METRIC_CHANGED","METRICS_CHANGED","FRAGMENT_LOADING_STARTED","FRAGMENT_LOADING_COMPLETED","LOG","PLAYBACK_TIME_UPDATED","PLAYBACK_PROGRESS"];for(var n in dashjs.MediaPlayer.events){if(o.indexOf(n)<0){i.dash.on(dashjs.MediaPlayer.events[n],(function(t){e.log("Player event fired: "+t.type)}))}}e.player.setSize=function(e){this.api.style.width=e.width+"px";this.api.style.height=e.height+"px"};e.player.api.setSource=function(t){e.player.dash.attachSource(t)};if(e.options.controls!="stock"){i.dash.updateSettings({streaming:{text:{defaultEnabled:false}}})}var l=false;i.dash.on("allTextTracksAdded",(function(){l=true}));e.player.api.setSubtitle=function(t){if(!l){var r=function(){e.player.api.setSubtitle(t);i.dash.off("allTextTracksAdded",r)};i.dash.on("allTextTracksAdded",r);return}if(!t){i.dash.enableText(false);return}var a=i.dash.getTracksFor("text");for(var s in a){var o="idx"in t?t.idx:t.trackid;if(a[s].id==o){i.dash.setTextTrack(s);if(!i.dash.isTextEnabled()){i.dash.enableText()}return true}}return false};MistUtil.event.addListener(r,"progress",(function(t){if(e.container.getAttribute("data-loading")=="stalled"){e.container.removeAttribute("data-loading")}}));i.api.unload=function(){i.dash.reset()};e.log("Built html");t(r)};if("dashjs"in window){this.onDashLoad()}else{var r=MistUtil.scripts.insert(e.urlappend(mistplayers.dashjs.scriptsrc(e.options.host)),{onerror:function(t){var i="Failed to load dashjs.js";if(t.message){i+=": "+t.message}e.showError(i)},onload:i.onDashLoad},e)}}; \ No newline at end of file diff --git a/embed/min/wrappers/flash_strobe.js b/embed/min/wrappers/flash_strobe.js index 8248445d..09013d5d 100644 --- a/embed/min/wrappers/flash_strobe.js +++ b/embed/min/wrappers/flash_strobe.js @@ -1 +1 @@ -mistplayers.flash_strobe={name:"Strobe Flash media playback",mimes:["flash/10","flash/11","flash/7"],priority:MistUtil.object.keys(mistplayers).length+1,isMimeSupported:function(t){return this.mimes.indexOf(t)==-1?false:true},isBrowserSupported:function(t,e,i){if(MistUtil.http.url.split(e.url).protocol.slice(0,4)=="http"&&location.protocol!=MistUtil.http.url.split(e.url).protocol){i.log("HTTP/HTTPS mismatch for this source");return false}var r=0;try{var a=navigator.mimeTypes["application/x-shockwave-flash"].enabledPlugin;if(a.version){r=a.version.split(".")[0]}else{r=a.description.replace(/([^0-9\.])/g,"").split(".")[0]}}catch(t){}try{r=new ActiveXObject("ShockwaveFlash.ShockwaveFlash").GetVariable("$version").replace(/([^0-9\,])/g,"").split(",")[0]}catch(t){}if(!r){return false}var l=t.split("/");return Number(r)>=Number(l[l.length-1])},player:function(){this.onreadylist=[]}};var p=mistplayers.flash_strobe.player;p.prototype=new MistPlayer;p.prototype.build=function(t,e){var i=document.createElement("object");var r=document.createElement("embed");i.appendChild(r);function a(e){var a=t.options;function l(t,e){var i=document.createElement("param");i.setAttribute("name",t);i.setAttribute("value",e);return i}MistUtil.empty(i);i.appendChild(l("movie",t.urlappend(a.host+t.source.player_url)));var o="src="+encodeURIComponent(e)+"&controlBarMode="+(a.controls?"floating":"none")+"&initialBufferTime=0.5&expandedBufferTime=5&minContinuousPlaybackTime=3"+(a.live?"&streamType=live":"")+(a.autoplay?"&autoPlay=true":"")+(a.loop?"&loop=true":"")+(a.poster?"&poster="+a.poster:"")+(a.muted?"&muted=true":"");i.appendChild(l("flashvars",o));i.appendChild(l("allowFullScreen","true"));i.appendChild(l("wmode","direct"));if(a.autoplay){i.appendChild(l("autoPlay","true"))}if(a.loop){i.appendChild(l("loop","true"))}if(a.poster){i.appendChild(l("poster",a.poster))}if(a.muted){i.appendChild(l("muted","true"))}r.setAttribute("src",t.urlappend(t.source.player_url));r.setAttribute("type","application/x-shockwave-flash");r.setAttribute("allowfullscreen","true");r.setAttribute("flashvars",o)}a(t.source.url);this.api={};this.setSize=function(t){i.setAttribute("width",t.width);i.setAttribute("height",t.height);r.setAttribute("width",t.width);r.setAttribute("height",t.height)};this.setSize(t.calcSize());this.onready(function(){if(t.container){t.container.removeAttribute("data-loading")}});this.api.setSource=function(t){a(t)};t.log("Built html");e(i)}; \ No newline at end of file +mistplayers.flash_strobe={name:"Strobe Flash media playback",mimes:["flash/10","flash/11","flash/7"],priority:MistUtil.object.keys(mistplayers).length+1,isMimeSupported:function(t){return this.mimes.indexOf(t)==-1?false:true},isBrowserSupported:function(t,e,i){if(MistUtil.http.url.split(e.url).protocol.slice(0,4)=="http"&&location.protocol!=MistUtil.http.url.split(e.url).protocol){i.log("HTTP/HTTPS mismatch for this source");return false}var r=0;try{var a=navigator.mimeTypes["application/x-shockwave-flash"].enabledPlugin;if(a.version){r=a.version.split(".")[0]}else{r=a.description.replace(/([^0-9\.])/g,"").split(".")[0]}}catch(t){}try{r=new ActiveXObject("ShockwaveFlash.ShockwaveFlash").GetVariable("$version").replace(/([^0-9\,])/g,"").split(",")[0]}catch(t){}if(!r){return false}var l=t.split("/");return Number(r)>=Number(l[l.length-1])},player:function(){this.onreadylist=[]}};var p=mistplayers.flash_strobe.player;p.prototype=new MistPlayer;p.prototype.build=function(t,e){var i=document.createElement("object");var r=document.createElement("embed");i.appendChild(r);function a(e){var a=t.options;function l(t,e){var i=document.createElement("param");i.setAttribute("name",t);i.setAttribute("value",e);return i}MistUtil.empty(i);i.appendChild(l("movie",t.urlappend(a.host+t.source.player_url)));var o="src="+encodeURIComponent(e)+"&controlBarMode="+(a.controls?"floating":"none")+"&initialBufferTime=0.5&expandedBufferTime=5&minContinuousPlaybackTime=3"+(a.live?"&streamType=live":"")+(a.autoplay?"&autoPlay=true":"")+(a.loop?"&loop=true":"")+(a.poster?"&poster="+a.poster:"")+(a.muted?"&muted=true":"");i.appendChild(l("flashvars",o));i.appendChild(l("allowFullScreen","true"));i.appendChild(l("wmode","direct"));if(a.autoplay){i.appendChild(l("autoPlay","true"))}if(a.loop){i.appendChild(l("loop","true"))}if(a.poster){i.appendChild(l("poster",a.poster))}if(a.muted){i.appendChild(l("muted","true"))}r.setAttribute("src",t.urlappend(t.source.player_url));r.setAttribute("type","application/x-shockwave-flash");r.setAttribute("allowfullscreen","true");r.setAttribute("flashvars",o)}a(t.source.url);this.api={};this.setSize=function(t){i.setAttribute("width",t.width);i.setAttribute("height",t.height);r.setAttribute("width",t.width);r.setAttribute("height",t.height)};this.setSize(t.calcSize());this.onready((function(){if(t.container){t.container.removeAttribute("data-loading")}}));this.api.setSource=function(t){a(t)};t.log("Built html");e(i)}; \ No newline at end of file diff --git a/embed/min/wrappers/flv.js b/embed/min/wrappers/flv.js index 72c2db72..9e980613 100644 --- a/embed/min/wrappers/flv.js +++ b/embed/min/wrappers/flv.js @@ -1 +1 @@ -mistplayers.flv={name:"HTML5 FLV Player",mimes:["flash/7"],priority:MistUtil.object.keys(mistplayers).length+1,isMimeSupported:function(e){return MistUtil.array.indexOf(this.mimes,e)==-1?false:true},isBrowserSupported:function(e,t,r){if(location.protocol!=MistUtil.http.url.split(t.url).protocol){if(location.protocol=="file:"&&MistUtil.http.url.split(t.url).protocol=="http:"){r.log("This page was loaded over file://, the player might not behave as intended.")}else{r.log("HTTP/HTTPS mismatch for this source");return false}}if(!window.MediaSource){return false}if(!MediaSource.isTypeSupported){return true}try{var o={};for(var a in r.info.meta.tracks){if(r.info.meta.tracks[a].type=="meta"){continue}if(!(r.info.meta.tracks[a].type in o)){o[r.info.meta.tracks[a].type]={}}o[r.info.meta.tracks[a].type][MistUtil.tracks.translateCodec(r.info.meta.tracks[a])]=1}var i=[];for(var l in o){var n=false;for(var s in o[l]){if(MediaSource.isTypeSupported('video/mp4;codecs="'+s+'"')){n=true;break}}if(n){i.push(l)}}t.supportedCodecs=i;return i.length?i:false}catch(e){}return false},player:function(){this.onreadylist=[]},scriptsrc:function(e){return e+"/flv.js"}};var p=mistplayers.flv.player;p.prototype=new MistPlayer;p.prototype.build=function(e,t){this.onFLVLoad=function(){if(e.destroyed){return}e.log("Building flv.js player..");var r=document.createElement("video");r.setAttribute("playsinline","");var o=["autoplay","loop","poster"];for(var a in o){var i=o[a];if(e.options[i]){r.setAttribute(i,e.options[i]===true?"":e.options[i])}}if(e.options.muted){r.muted=true}if(e.options.controls=="stock"){r.setAttribute("controls","")}if(e.info.type=="live"){r.loop=false}flvjs.LoggingControl.applyConfig({enableVerbose:false});flvjs.LoggingControl.addLogListener(function(t,r){e.log("[flvjs] "+r)});var l={type:"flv",url:e.source.url,hasAudio:false,hasVideo:false};for(var a in e.source.supportedCodecs){l["has"+e.source.supportedCodecs[a].charAt(0).toUpperCase()+e.source.supportedCodecs[a].slice(1)]=true}e.player.create=function(t){t=MistUtil.object.extend({},t);e.player.flvPlayer=flvjs.createPlayer(t,{lazyLoad:false});e.player.flvPlayer.attachMediaElement(r);e.player.flvPlayer.load();e.player.flvPlayer.play();if(!e.options.autoplay){r.pause()}};e.player.create(l);e.player.api={};function n(t){Object.defineProperty(e.player.api,t,{get:function(){return r[t]},set:function(e){return r[t]=e}})}var s=["volume","buffered","muted","loop","paused",,"error","textTracks","webkitDroppedFrameCount","webkitDecodedFrameCount"];if(e.info.type!="live"){s.push("duration")}else{Object.defineProperty(e.player.api,"duration",{get:function(){if(!r.buffered.length){return 0}return r.buffered.end(r.buffered.length-1)}})}for(var a in s){n(s[a])}function f(t){if(t in r){e.player.api[t]=function(){return r[t].call(r,arguments)}}}var s=["load","getVideoPlaybackQuality","play","pause"];for(var a in s){f(s[a])}e.player.api.setSource=function(t){if(t!=l.url&&t!=""){e.player.flvPlayer.unload();e.player.flvPlayer.detachMediaElement();e.player.flvPlayer.destroy();l.url=t;e.player.create(l)}};e.player.api.unload=function(){e.player.flvPlayer.unload();e.player.flvPlayer.detachMediaElement();e.player.flvPlayer.destroy()};e.player.setSize=function(e){r.style.width=e.width+"px";r.style.height=e.height+"px"};Object.defineProperty(e.player.api,"currentTime",{get:function(){return r.currentTime},set:function(t){var o=.5;for(var a=0;a=r.buffered.start(a)&&t<=r.buffered.end(a)-o){return r.currentTime=t}}e.log("Seek attempted outside of buffer, but MistServer does not support seeking in progressive flash. Setting to closest available instead");return r.currentTime=r.buffered.length?r.buffered.end(r.buffered.length-1)-o:0}});t(r)};if("flvjs"in window){this.onFLVLoad()}else{var r=MistUtil.scripts.insert(e.urlappend(mistplayers.flv.scriptsrc(e.options.host)),{onerror:function(t){var r="Failed to load flv.js";if(t.message){r+=": "+t.message}e.showError(r)},onload:e.player.onFLVLoad},e)}}; \ No newline at end of file +mistplayers.flv={name:"HTML5 FLV Player",mimes:["flash/7"],priority:MistUtil.object.keys(mistplayers).length+1,isMimeSupported:function(e){return MistUtil.array.indexOf(this.mimes,e)==-1?false:true},isBrowserSupported:function(e,t,r){if(location.protocol!=MistUtil.http.url.split(t.url).protocol){if(location.protocol=="file:"&&MistUtil.http.url.split(t.url).protocol=="http:"){r.log("This page was loaded over file://, the player might not behave as intended.")}else{r.log("HTTP/HTTPS mismatch for this source");return false}}if(!window.MediaSource){return false}if(!MediaSource.isTypeSupported){return true}try{var o={};for(var a in r.info.meta.tracks){if(r.info.meta.tracks[a].type=="meta"){continue}if(!(r.info.meta.tracks[a].type in o)){o[r.info.meta.tracks[a].type]={}}o[r.info.meta.tracks[a].type][MistUtil.tracks.translateCodec(r.info.meta.tracks[a])]=1}var i=[];for(var l in o){var n=false;for(var s in o[l]){if(MediaSource.isTypeSupported('video/mp4;codecs="'+s+'"')){n=true;break}}if(n){i.push(l)}}t.supportedCodecs=i;return i.length?i:false}catch(e){}return false},player:function(){this.onreadylist=[]},scriptsrc:function(e){return e+"/flv.js"}};var p=mistplayers.flv.player;p.prototype=new MistPlayer;p.prototype.build=function(e,t){this.onFLVLoad=function(){if(e.destroyed){return}e.log("Building flv.js player..");var r=document.createElement("video");r.setAttribute("playsinline","");var o=["autoplay","loop","poster"];for(var a in o){var i=o[a];if(e.options[i]){r.setAttribute(i,e.options[i]===true?"":e.options[i])}}if(e.options.muted){r.muted=true}if(e.options.controls=="stock"){r.setAttribute("controls","")}if(e.info.type=="live"){r.loop=false}flvjs.LoggingControl.applyConfig({enableVerbose:false});flvjs.LoggingControl.addLogListener((function(t,r){e.log("[flvjs] "+r)}));var l={type:"flv",url:e.source.url,hasAudio:false,hasVideo:false};for(var a in e.source.supportedCodecs){l["has"+e.source.supportedCodecs[a].charAt(0).toUpperCase()+e.source.supportedCodecs[a].slice(1)]=true}e.player.create=function(t){t=MistUtil.object.extend({},t);e.player.flvPlayer=flvjs.createPlayer(t,{lazyLoad:false});e.player.flvPlayer.attachMediaElement(r);e.player.flvPlayer.load();e.player.flvPlayer.play();if(!e.options.autoplay){r.pause()}};e.player.create(l);e.player.api={};function n(t){Object.defineProperty(e.player.api,t,{get:function(){return r[t]},set:function(e){return r[t]=e}})}var s=["volume","buffered","muted","loop","paused",,"error","textTracks","webkitDroppedFrameCount","webkitDecodedFrameCount"];if(e.info.type!="live"){s.push("duration")}else{Object.defineProperty(e.player.api,"duration",{get:function(){if(!r.buffered.length){return 0}return r.buffered.end(r.buffered.length-1)}})}for(var a in s){n(s[a])}function f(t){if(t in r){e.player.api[t]=function(){return r[t].call(r,arguments)}}}var s=["load","getVideoPlaybackQuality","play","pause"];for(var a in s){f(s[a])}e.player.api.setSource=function(t){if(t!=l.url&&t!=""){e.player.flvPlayer.unload();e.player.flvPlayer.detachMediaElement();e.player.flvPlayer.destroy();l.url=t;e.player.create(l)}};e.player.api.unload=function(){e.player.flvPlayer.unload();e.player.flvPlayer.detachMediaElement();e.player.flvPlayer.destroy()};e.player.setSize=function(e){r.style.width=e.width+"px";r.style.height=e.height+"px"};Object.defineProperty(e.player.api,"currentTime",{get:function(){return r.currentTime},set:function(t){var o=.5;for(var a=0;a=r.buffered.start(a)&&t<=r.buffered.end(a)-o){return r.currentTime=t}}e.log("Seek attempted outside of buffer, but MistServer does not support seeking in progressive flash. Setting to closest available instead");return r.currentTime=r.buffered.length?r.buffered.end(r.buffered.length-1)-o:0}});t(r)};if("flvjs"in window){this.onFLVLoad()}else{var r=MistUtil.scripts.insert(e.urlappend(mistplayers.flv.scriptsrc(e.options.host)),{onerror:function(t){var r="Failed to load flv.js";if(t.message){r+=": "+t.message}e.showError(r)},onload:e.player.onFLVLoad},e)}}; \ No newline at end of file diff --git a/embed/min/wrappers/hlsjs.js b/embed/min/wrappers/hlsjs.js index 195c5883..d13425e7 100644 --- a/embed/min/wrappers/hlsjs.js +++ b/embed/min/wrappers/hlsjs.js @@ -1 +1 @@ -mistplayers.hlsjs={name:"HLS.js player",mimes:["html5/application/vnd.apple.mpegurl","html5/application/vnd.apple.mpegurl;version=7"],priority:MistUtil.object.keys(mistplayers).length+1,isMimeSupported:function(t){return this.mimes.indexOf(t)==-1?false:true},isBrowserSupported:function(t,e,r){if(location.protocol!=MistUtil.http.url.split(e.url).protocol){r.log("HTTP/HTTPS mismatch for this source");return false}if(!("MediaSource"in window)){return false}if(!MediaSource.isTypeSupported){return true}var i={};var s=false;for(var o in r.info.meta.tracks){if(r.info.meta.tracks[o].type=="meta"){if(r.info.meta.tracks[o].codec=="subtitle"){s=true}continue}if(!(r.info.meta.tracks[o].type in i)){i[r.info.meta.tracks[o].type]={}}i[r.info.meta.tracks[o].type][MistUtil.tracks.translateCodec(r.info.meta.tracks[o])]=1}var a=[];for(var l in i){var n=false;for(var p in i[l]){if(MediaSource.isTypeSupported('video/mp4;codecs="'+p+'"')){n=true;break}}if(n){a.push(l)}}if(s){for(var o in r.info.source){if(r.info.source[o].type=="html5/text/vtt"){a.push("subtitle");break}}}return a.length?a:false},player:function(){},scriptsrc:function(t){return t+"/hlsjs.js"}};var p=mistplayers.hlsjs.player;p.prototype=new MistPlayer;p.prototype.build=function(t,e){var r=this;var i=document.createElement("video");i.setAttribute("playsinline","");var s=["autoplay","loop","poster"];for(var o in s){var a=s[o];if(t.options[a]){i.setAttribute(a,t.options[a]===true?"":t.options[a])}}if(t.options.muted){i.muted=true}if(t.info.type=="live"){i.loop=false}if(t.options.controls=="stock"){i.setAttribute("controls","")}i.setAttribute("crossorigin","anonymous");this.setSize=function(t){i.style.width=t.width+"px";i.style.height=t.height+"px"};this.api=i;t.player.api.unload=function(){if(t.player.hls){t.player.hls.destroy();t.player.hls=false;t.log("hls.js instance disposed")}};function l(e){t.player.hls=new Hls({maxBufferLength:15,maxMaxBufferLength:60});t.player.hls.attachMedia(i);t.player.hls.on(Hls.Events.MEDIA_ATTACHED,function(){t.player.hls.loadSource(e)})}t.player.api.setSource=function(e){if(!t.player.hls){return}if(t.player.hls.url!=e){t.player.hls.destroy();l(e)}};t.player.api.setSubtitle=function(t){var e=i.getElementsByTagName("track");for(var r=e.length-1;r>=0;r--){i.removeChild(e[r])}if(t){var s=document.createElement("track");i.appendChild(s);s.kind="subtitles";s.label=t.label;s.srclang=t.lang;s.src=t.src;s.setAttribute("default","")}};function n(){l(t.source.url)}if("Hls"in window){n()}else{var p=t.urlappend(mistplayers.hlsjs.scriptsrc(t.options.host));MistUtil.scripts.insert(p,{onerror:function(e){var r="Failed to load hlsjs.js";if(e.message){r+=": "+e.message}t.showError(r)},onload:n},t)}e(i)}; \ No newline at end of file +mistplayers.hlsjs={name:"HLS.js player",mimes:["html5/application/vnd.apple.mpegurl","html5/application/vnd.apple.mpegurl;version=7"],priority:MistUtil.object.keys(mistplayers).length+1,isMimeSupported:function(t){return this.mimes.indexOf(t)==-1?false:true},isBrowserSupported:function(t,e,r){if(location.protocol!=MistUtil.http.url.split(e.url).protocol){r.log("HTTP/HTTPS mismatch for this source");return false}if(!("MediaSource"in window)){return false}if(!MediaSource.isTypeSupported){return true}var i={};var s=false;for(var o in r.info.meta.tracks){if(r.info.meta.tracks[o].type=="meta"){if(r.info.meta.tracks[o].codec=="subtitle"){s=true}continue}if(!(r.info.meta.tracks[o].type in i)){i[r.info.meta.tracks[o].type]={}}i[r.info.meta.tracks[o].type][MistUtil.tracks.translateCodec(r.info.meta.tracks[o])]=1}var a=[];for(var l in i){var n=false;for(var p in i[l]){if(MediaSource.isTypeSupported('video/mp4;codecs="'+p+'"')){n=true;break}}if(n){a.push(l)}}if(s){for(var o in r.info.source){if(r.info.source[o].type=="html5/text/vtt"){a.push("subtitle");break}}}return a.length?a:false},player:function(){},scriptsrc:function(t){return t+"/hlsjs.js"}};var p=mistplayers.hlsjs.player;p.prototype=new MistPlayer;p.prototype.build=function(t,e){var r=this;var i=document.createElement("video");i.setAttribute("playsinline","");var s=["autoplay","loop","poster"];for(var o in s){var a=s[o];if(t.options[a]){i.setAttribute(a,t.options[a]===true?"":t.options[a])}}if(t.options.muted){i.muted=true}if(t.info.type=="live"){i.loop=false}if(t.options.controls=="stock"){i.setAttribute("controls","")}i.setAttribute("crossorigin","anonymous");this.setSize=function(t){i.style.width=t.width+"px";i.style.height=t.height+"px"};this.api=i;t.player.api.unload=function(){if(t.player.hls){t.player.hls.destroy();t.player.hls=false;t.log("hls.js instance disposed")}};function l(e){t.player.hls=new Hls({maxBufferLength:15,maxMaxBufferLength:60});t.player.hls.attachMedia(i);t.player.hls.on(Hls.Events.MEDIA_ATTACHED,(function(){t.player.hls.loadSource(e)}))}t.player.api.setSource=function(e){if(!t.player.hls){return}if(t.player.hls.url!=e){t.player.hls.destroy();l(e)}};t.player.api.setSubtitle=function(t){var e=i.getElementsByTagName("track");for(var r=e.length-1;r>=0;r--){i.removeChild(e[r])}if(t){var s=document.createElement("track");i.appendChild(s);s.kind="subtitles";s.label=t.label;s.srclang=t.lang;s.src=t.src;s.setAttribute("default","")}};function n(){l(t.source.url)}if("Hls"in window){n()}else{var p=t.urlappend(mistplayers.hlsjs.scriptsrc(t.options.host));MistUtil.scripts.insert(p,{onerror:function(e){var r="Failed to load hlsjs.js";if(e.message){r+=": "+e.message}t.showError(r)},onload:n},t)}e(i)}; \ No newline at end of file diff --git a/embed/min/wrappers/html5.js b/embed/min/wrappers/html5.js index ac29029f..aa8de371 100644 --- a/embed/min/wrappers/html5.js +++ b/embed/min/wrappers/html5.js @@ -1 +1 @@ -mistplayers.html5={name:"HTML5 video player",mimes:["html5/application/vnd.apple.mpegurl","html5/application/vnd.apple.mpegurl;version=7","html5/video/mp4","html5/video/ogg","html5/video/webm","html5/audio/mp3","html5/audio/webm","html5/audio/ogg","html5/audio/wav"],priority:MistUtil.object.keys(mistplayers).length+1,isMimeSupported:function(e){return MistUtil.array.indexOf(this.mimes,e)==-1?false:true},isBrowserSupported:function(e,t,i){if(location.protocol!=MistUtil.http.url.split(t.url).protocol){if(location.protocol=="file:"&&MistUtil.http.url.split(t.url).protocol=="http:"){i.log("This page was loaded over file://, the player might not behave as intended.")}else{i.log("HTTP/HTTPS mismatch for this source");return false}}if(e=="html5/application/vnd.apple.mpegurl"){var r=MistUtil.getAndroid();if(r&&parseFloat(r)<7){i.log("Skipping native HLS as videojs will do better");return false}}var a=false;var n=e.split("/");n.shift();try{n=n.join("/");function o(e){if(e.codecstring){return e.codecstring}function t(t){return("0"+e.init.charCodeAt(t).toString(16)).slice(-2)}switch(e.codec){case"AAC":return"mp4a.40.2";case"MP3":return"mp4a.40.34";case"AC3":return"ec-3";case"H264":return"avc1."+t(1)+t(2)+t(3);case"HEVC":return"hev1."+t(1)+t(6)+t(7)+t(8)+t(9)+t(10)+t(11)+t(12);default:return e.codec.toLowerCase()}}var s={};var l={};var p=false;for(var u in i.info.meta.tracks){if(i.info.meta.tracks[u].type!="meta"){s[o(i.info.meta.tracks[u])]=i.info.meta.tracks[u]}else if(i.info.meta.tracks[u].codec=="subtitle"){p=true}}var f=e.split("/")[2];t.supportedCodecs=[];for(var u in s){var c=d(u);if(c){t.supportedCodecs.push(s[u].codec);l[s[u].type]=1}}function d(e){var t=document.createElement("video");if(t&&typeof t.canPlayType=="function"){var i;switch(n){case"video/webm":{i=t.canPlayType(n);break}case"video/mp4":case"html5/application/vnd.apple.mpegurl":default:{i=t.canPlayType(n+';codecs="'+e+'"');break}}if(i!=""){return i}}return false}if(p){for(var u in i.info.source){if(i.info.source[u].type=="html5/text/vtt"){l.subtitle=1;break}}}a=MistUtil.object.keys(l)}catch(e){}return a},player:function(){this.onreadylist=[]},mistControls:true};var p=mistplayers.html5.player;p.prototype=new MistPlayer;p.prototype.build=function(e,t){var i=e.source.type.split("/");i.shift();var r=document.createElement("video");r.setAttribute("crossorigin","anonymous");r.setAttribute("playsinline","");var a=document.createElement("source");a.setAttribute("src",e.source.url);r.source=a;r.appendChild(a);a.type=i.join("/");var n=["autoplay","loop","poster"];for(var o in n){var s=n[o];if(e.options[s]){r.setAttribute(s,e.options[s]===true?"":e.options[s])}}if(e.options.muted){r.muted=true}if(e.options.controls=="stock"){r.setAttribute("controls","")}if(e.info.type=="live"){r.loop=false}if("Proxy"in window&&"Reflect"in window){var l={get:{},set:{}};e.player.api=new Proxy(r,{get:function(e,t,i){if(t in l.get){return l.get[t].apply(e,arguments)}var r=e[t];if(typeof r==="function"){return function(){return r.apply(e,arguments)}}return r},set:function(e,t,i){if(t in l.set){return l.set[t].call(e,i)}return e[t]=i}});if(e.source.type=="html5/audio/mp3"){l.set.currentTime=function(){e.log("Seek attempted, but MistServer does not currently support seeking in MP3.");return false}}if(e.info.type=="live"){l.get.duration=function(){var t=0;if(this.buffered.length){t=this.buffered.end(this.buffered.length-1)}var i=((new Date).getTime()-e.player.api.lastProgress.getTime())*.001;return t+i-e.player.api.liveOffset};l.set.currentTime=function(t){var i=t-e.player.api.duration;if(i>0){i=0}e.player.api.liveOffset=i;e.log("Seeking to "+MistUtil.format.time(t)+" ("+Math.round(i*-10)/10+"s from live)");var r={startunix:i};if(i==0){r={}}e.player.api.setSource(MistUtil.http.url.addParam(e.source.url,r))};MistUtil.event.addListener(r,"progress",function(){e.player.api.lastProgress=new Date});e.player.api.lastProgress=new Date;e.player.api.liveOffset=0;MistUtil.event.addListener(r,"pause",function(){e.player.api.pausedAt=new Date});l.get.play=function(){return function(){if(e.player.api.paused&&e.player.api.pausedAt&&new Date-e.player.api.pausedAt>5e3){r.load();e.log("Reloading source..")}return r.play.apply(r,arguments)}};if(e.source.type=="html5/video/mp4"){var p=l.get.duration;l.get.duration=function(){return p.apply(this,arguments)-e.player.api.liveOffset+e.info.lastms*.001};l.get.currentTime=function(){return this.currentTime-e.player.api.liveOffset+e.info.lastms*.001};l.get.buffered=function(){var t=this;return{length:t.buffered.length,start:function(i){return t.buffered.start(i)-e.player.api.liveOffset+e.info.lastms*.001},end:function(i){return t.buffered.end(i)-e.player.api.liveOffset+e.info.lastms*.001}}}}}else{if(!isFinite(r.duration)){var u=0;for(var o in e.info.meta.tracks){u=Math.max(u,e.info.meta.tracks[o].lastms)}l.get.duration=function(){if(isFinite(this.duration)){return this.duration}return u*.001}}}}else{e.player.api=r}e.player.api.setSource=function(e){if(e!=this.source.src){this.source.src=e;this.load()}};e.player.api.setSubtitle=function(e){var t=r.getElementsByTagName("track");for(var i=t.length-1;i>=0;i--){r.removeChild(t[i])}if(e){var a=document.createElement("track");r.appendChild(a);a.kind="subtitles";a.label=e.label;a.srclang=e.lang;a.src=e.src;a.setAttribute("default","")}};e.player.setSize=function(e){this.api.style.width=e.width+"px";this.api.style.height=e.height+"px"};t(r)}; \ No newline at end of file +mistplayers.html5={name:"HTML5 video player",mimes:["html5/application/vnd.apple.mpegurl","html5/application/vnd.apple.mpegurl;version=7","html5/video/mp4","html5/video/ogg","html5/video/webm","html5/audio/mp3","html5/audio/webm","html5/audio/ogg","html5/audio/wav"],priority:MistUtil.object.keys(mistplayers).length+1,isMimeSupported:function(e){return MistUtil.array.indexOf(this.mimes,e)==-1?false:true},isBrowserSupported:function(e,t,i){if(location.protocol!=MistUtil.http.url.split(t.url).protocol){if(location.protocol=="file:"&&MistUtil.http.url.split(t.url).protocol=="http:"){i.log("This page was loaded over file://, the player might not behave as intended.")}else{i.log("HTTP/HTTPS mismatch for this source");return false}}if(e=="html5/application/vnd.apple.mpegurl"){var r=MistUtil.getAndroid();if(r&&parseFloat(r)<7){i.log("Skipping native HLS as videojs will do better");return false}}var a=false;var n=e.split("/");n.shift();try{n=n.join("/");function o(e){if(e.codecstring){return e.codecstring}function t(t){return("0"+e.init.charCodeAt(t).toString(16)).slice(-2)}switch(e.codec){case"AAC":return"mp4a.40.2";case"MP3":return"mp4a.40.34";case"AC3":return"ec-3";case"H264":return"avc1."+t(1)+t(2)+t(3);case"HEVC":return"hev1."+t(1)+t(6)+t(7)+t(8)+t(9)+t(10)+t(11)+t(12);default:return e.codec.toLowerCase()}}var s={};var l={};var p=false;for(var u in i.info.meta.tracks){if(i.info.meta.tracks[u].type!="meta"){s[o(i.info.meta.tracks[u])]=i.info.meta.tracks[u]}else if(i.info.meta.tracks[u].codec=="subtitle"){p=true}}var f=e.split("/")[2];t.supportedCodecs=[];for(var u in s){var c=d(u);if(c){t.supportedCodecs.push(s[u].codec);l[s[u].type]=1}}function d(e){var t=document.createElement("video");if(t&&typeof t.canPlayType=="function"){var i;switch(n){case"video/webm":{i=t.canPlayType(n);break}case"video/mp4":case"html5/application/vnd.apple.mpegurl":default:{i=t.canPlayType(n+';codecs="'+e+'"');break}}if(i!=""){return i}}return false}if(p){for(var u in i.info.source){if(i.info.source[u].type=="html5/text/vtt"){l.subtitle=1;break}}}a=MistUtil.object.keys(l)}catch(m){}return a},player:function(){this.onreadylist=[]},mistControls:true};var p=mistplayers.html5.player;p.prototype=new MistPlayer;p.prototype.build=function(e,t){var i=e.source.type.split("/");i.shift();var r=document.createElement("video");r.setAttribute("crossorigin","anonymous");r.setAttribute("playsinline","");var a=document.createElement("source");a.setAttribute("src",e.source.url);r.source=a;r.appendChild(a);a.type=i.join("/");var n=["autoplay","loop","poster"];for(var o in n){var s=n[o];if(e.options[s]){r.setAttribute(s,e.options[s]===true?"":e.options[s])}}if(e.options.muted){r.muted=true}if(e.options.controls=="stock"){r.setAttribute("controls","")}if(e.info.type=="live"){r.loop=false}if("Proxy"in window&&"Reflect"in window){var l={get:{},set:{}};e.player.api=new Proxy(r,{get:function(e,t,i){if(t in l.get){return l.get[t].apply(e,arguments)}var r=e[t];if(typeof r==="function"){return function(){return r.apply(e,arguments)}}return r},set:function(e,t,i){if(t in l.set){return l.set[t].call(e,i)}return e[t]=i}});if(e.source.type=="html5/audio/mp3"){l.set.currentTime=function(){e.log("Seek attempted, but MistServer does not currently support seeking in MP3.");return false}}if(e.info.type=="live"){l.get.duration=function(){var t=0;if(this.buffered.length){t=this.buffered.end(this.buffered.length-1)}var i=((new Date).getTime()-e.player.api.lastProgress.getTime())*.001;return t+i-e.player.api.liveOffset};l.set.currentTime=function(t){var i=t-e.player.api.duration;if(i>0){i=0}e.player.api.liveOffset=i;e.log("Seeking to "+MistUtil.format.time(t)+" ("+Math.round(i*-10)/10+"s from live)");var r={startunix:i};if(i==0){r={}}e.player.api.setSource(MistUtil.http.url.addParam(e.source.url,r))};MistUtil.event.addListener(r,"progress",(function(){e.player.api.lastProgress=new Date}));e.player.api.lastProgress=new Date;e.player.api.liveOffset=0;MistUtil.event.addListener(r,"pause",(function(){e.player.api.pausedAt=new Date}));l.get.play=function(){return function(){if(e.player.api.paused&&e.player.api.pausedAt&&new Date-e.player.api.pausedAt>5e3){r.load();e.log("Reloading source..")}return r.play.apply(r,arguments)}};if(e.source.type=="html5/video/mp4"){var p=l.get.duration;l.get.duration=function(){return p.apply(this,arguments)-e.player.api.liveOffset+e.info.lastms*.001};l.get.currentTime=function(){return this.currentTime-e.player.api.liveOffset+e.info.lastms*.001};l.get.buffered=function(){var t=this;return{length:t.buffered.length,start:function(i){return t.buffered.start(i)-e.player.api.liveOffset+e.info.lastms*.001},end:function(i){return t.buffered.end(i)-e.player.api.liveOffset+e.info.lastms*.001}}}}}else{if(!isFinite(r.duration)){var u=0;for(var o in e.info.meta.tracks){u=Math.max(u,e.info.meta.tracks[o].lastms)}l.get.duration=function(){if(isFinite(this.duration)){return this.duration}return u*.001}}}}else{e.player.api=r}e.player.api.setSource=function(e){if(e!=this.source.src){this.source.src=e;this.load()}};e.player.api.setSubtitle=function(e){var t=r.getElementsByTagName("track");for(var i=t.length-1;i>=0;i--){r.removeChild(t[i])}if(e){var a=document.createElement("track");r.appendChild(a);a.kind="subtitles";a.label=e.label;a.srclang=e.lang;a.src=e.src;a.setAttribute("default","")}};e.player.setSize=function(e){this.api.style.width=e.width+"px";this.api.style.height=e.height+"px"};t(r)}; \ No newline at end of file diff --git a/embed/min/wrappers/mews.js b/embed/min/wrappers/mews.js index 194b355b..ff57a3e5 100644 --- a/embed/min/wrappers/mews.js +++ b/embed/min/wrappers/mews.js @@ -1 +1 @@ -mistplayers.mews={name:"MSE websocket player",mimes:["ws/video/mp4","ws/video/webm"],priority:MistUtil.object.keys(mistplayers).length+1,isMimeSupported:function(e){return this.mimes.indexOf(e)==-1?false:true},isBrowserSupported:function(e,t,i){if(!("WebSocket"in window)||!("MediaSource"in window)||!("Promise"in window)){return false}if(location.protocol.replace(/^http/,"ws")!=MistUtil.http.url.split(t.url.replace(/^http/,"ws")).protocol){i.log("HTTP/HTTPS mismatch for this source");return false}if(navigator.platform.toUpperCase().indexOf("MAC")>=0){return false}function r(e){if(e.codecstring){return e.codecstring}function t(t){return("0"+e.init.charCodeAt(t).toString(16)).slice(-2)}switch(e.codec){case"AAC":return"mp4a.40.2";case"MP3":return"mp4a.40.34";case"AC3":return"ec-3";case"H264":return"avc1."+t(1)+t(2)+t(3);case"HEVC":return"hev1."+t(1)+t(6)+t(7)+t(8)+t(9)+t(10)+t(11)+t(12);default:return e.codec.toLowerCase()}}var n={};var s={};var a=false;for(var o in i.info.meta.tracks){if(i.info.meta.tracks[o].type!="meta"){n[r(i.info.meta.tracks[o])]=i.info.meta.tracks[o]}else if(i.info.meta.tracks[o].codec=="subtitle"){a=true}}var u=e.split("/")[2];function d(e){return MediaSource.isTypeSupported("video/"+u+';codecs="'+e+'"')}t.supportedCodecs=[];for(var o in n){var c=d(o);if(c){t.supportedCodecs.push(n[o].codec);s[n[o].type]=1}}if(a){for(var o in i.info.source){if(i.info.source[o].type=="html5/text/vtt"){s.subtitle=1;break}}}return MistUtil.object.keys(s)},player:function(){}};var p=mistplayers.mews.player;p.prototype=new MistPlayer;p.prototype.build=function(e,t){var i=document.createElement("video");i.setAttribute("playsinline","");var r=["autoplay","loop","poster"];for(var n in r){var s=r[n];if(e.options[s]){i.setAttribute(s,e.options[s]===true?"":e.options[s])}}if(e.options.muted){i.muted=true}if(e.info.type=="live"){i.loop=false}if(e.options.controls=="stock"){i.setAttribute("controls","")}i.setAttribute("crossorigin","anonymous");this.setSize=function(e){i.style.width=e.width+"px";i.style.height=e.height+"px"};var a=this;a.built=false;function o(){if(a.ws.readyState==a.ws.OPEN&&a.ms.readyState=="open"&&a.sb){if(!a.built){t(i);a.built=true}if(e.options.autoplay){a.api.play().catch(function(){})}return true}}this.msoninit=[];this.msinit=function(){return new Promise(function(e,t){a.ms=new MediaSource;i.src=URL.createObjectURL(a.ms);a.ms.onsourceopen=function(){for(var t in a.msoninit){a.msoninit[t]()}a.msoninit=[];e()};a.ms.onsourceclose=function(e){if(a.debugging)console.error("ms close",e);u({type:"stop"})};a.ms.onsourceended=function(e){if(a.debugging)console.error("ms ended",e);if(a.debugging=="dl"){function t(e,t,r){var n,s;n=new Blob([e],{type:r});s=window.URL.createObjectURL(n);i(s,t);setTimeout(function(){return window.URL.revokeObjectURL(s)},1e3)}function i(e,t){var i;i=document.createElement("a");i.href=e;i.download=t;document.body.appendChild(i);i.style="display: none";i.click();i.remove()}var r=0;for(var n=0;n=500){n=0;a.sb._clean(10)}else{n++}var t=r.slice();r=[];for(var s in t){if(!a.sb){if(a.debugging){console.warn("I was doing on_updateend but the sb was reset")}break}if(a.sb.updating){r.concat(t.slice(s));if(a.debugging){console.warn("I was doing on_updateend but was interrupted")}break}t[s](s0&&!a.sb.updating&&!i.error){a.sb._append(this.queue.shift())}});a.sb.error=function(e){console.error("sb error",e)};a.sb.abort=function(e){console.error("sb abort",e)};a.sb._doNext=function(e){r.push(e)};a.sb._do=function(e){if(this.updating||this._busy){this._doNext(e)}else{e()}};a.sb._append=function(t){if(!t){return}if(!t.buffer){return}if(a.debugging){a.sb.appending=new Uint8Array(t)}if(a.sb._busy){if(a.debugging)console.warn("I wanted to append data, but now I won't because the thingy was still busy. Putting it back in the queue.");a.sb.queue.unshift(t);return}a.sb._busy=true;try{a.sb.appendBuffer(t)}catch(n){switch(n.name){case"QuotaExceededError":{if(i.buffered.length){if(i.currentTime-i.buffered.start(0)>1){e.log("Triggered QuotaExceededError: cleaning up "+Math.round((i.currentTime-i.buffered.start(0)-1)*10)/10+"s");a.sb._clean(1)}else{var r=i.buffered.end(i.buffered.length-1);e.log("Triggered QuotaExceededError but there is nothing to clean: skipping ahead "+Math.round((r-i.currentTime)*10)/10+"s");i.currentTime=r}a.sb._busy=false;a.sb._append(t);return}break}case"InvalidStateError":{a.api.pause();if(e.video.error){return}break}}e.showError(n.message)}};if(a.msgqueue){if(a.msgqueue[0]){var s=false;if(a.msgqueue[0].length){for(var u in a.msgqueue[0]){if(a.sb.updating||a.sb.queue.length||a.sb._busy){a.sb.queue.push(a.msgqueue[0][u])}else{a.sb._append(a.msgqueue[0][u])}}}else{s=true}a.msgqueue.shift();if(a.msgqueue.length==0){a.msgqueue=false}e.log("The newly initialized source buffer was filled with data from a separate message queue."+(a.msgqueue?" "+a.msgqueue.length+" more message queue(s) remain.":""));if(s){e.log("The separate message queue was empty; manually triggering any onupdateend functions");a.sb.dispatchEvent(new Event("updateend"))}}}a.sb._clean=function(e){if(!e)e=180;if(i.currentTime>e){a.sb._do(function(){a.sb.remove(0,Math.max(.1,i.currentTime-e))})}};if(a.onsbinit.length){a.onsbinit.shift()()}o()};this.wsconnect=function(){return new Promise(function(t,r){this.ws=new WebSocket(e.source.url);this.ws.binaryType="arraybuffer";this.ws.s=this.ws.send;this.ws.send=function(){if(this.readyState==1){this.s.apply(this,arguments);return true}return false};this.ws.onopen=function(){this.wasConnected=true;t()};this.ws.onerror=function(t){e.showError("MP4 over WS: websocket error")};this.ws.onclose=function(t){e.log("MP4 over WS: websocket closed");if(this.wasConnected&&!e.destroyed&&(!a.sb||!a.sb.paused)&&e.state=="Stream is online"&&!(e.video&&e.video.error)){e.log("MP4 over WS: reopening websocket");a.wsconnect().then(function(){if(!a.sb){var t=function(e){if(!a.sb){a.sbinit(e.data.codecs)}else{a.api.play().catch(function(){})}a.ws.removeListener("codec_data",t)};a.ws.addListener("codec_data",t);u({type:"request_codec_data",supported_codecs:e.source.supportedCodecs})}else{a.api.play()}},function(){Mistvideo.error("Lost connection to the Media Server")})}};this.ws.timeOut=e.timers.start(function(){if(a.ws.readyState==0){e.log("MP4 over WS: socket timeout - try next combo");e.nextCombo()}},5e3);this.ws.listeners={};this.ws.addListener=function(e,t){if(!(e in this.listeners)){this.listeners[e]=[]}this.listeners[e].push(t)};this.ws.removeListener=function(e,t){if(!(e in this.listeners)){return}var i=this.listeners[e].indexOf(t);if(i<0){return}this.listeners[e].splice(i,1);return true};a.msgqueue=false;var n=1;var s=[];var o=[];this.ws.onmessage=function(t){if(!t.data){throw"Received invalid data"}if(typeof t.data=="string"){var r=JSON.parse(t.data);if(a.debugging&&r.type!="on_time"){console.log("ws message",r)}switch(r.type){case"on_stop":{var s;s=MistUtil.event.addListener(i,"waiting",function(e){a.sb.paused=true;MistUtil.event.send("ended",null,i);MistUtil.event.removeListener(s)});a.ws.onclose=function(){};break}case"on_time":{var c=r.data.current-i.currentTime*1e3;var f=a.ws.serverDelay.get();var l=Math.max(100+f,f*2);var p=l+(r.data.jitter?r.data.jitter:0);if(e.info.type!="live"){l+=2e3}if(a.debugging){console.log("on_time received",r.data.current/1e3,"currtime",i.currentTime,n+"x","buffer",Math.round(c),"/",Math.round(l),e.info.type=="live"?"latency:"+Math.round(r.data.end-i.currentTime*1e3)+"ms":"",a.monitor?"bitrate:"+MistUtil.format.bits(a.monitor.currentBps)+"/s":"","listeners",a.ws.listeners&&a.ws.listeners.on_time?a.ws.listeners.on_time:0,"msgqueue",a.msgqueue?a.msgqueue.length:0,"readyState",e.video.readyState,r.data)}if(!a.sb){e.log("Received on_time, but the source buffer is being cleared right now. Ignoring.");break}if(d!=r.data.end*.001){d=r.data.end*.001;MistUtil.event.send("durationchange",null,e.video)}e.info.meta.buffer_window=r.data.end-r.data.begin;a.sb.paused=false;if(e.info.type=="live"){if(n==1){if(r.data.play_rate_curr=="auto"){if(i.currentTime>0){if(c>p*2){n=1+Math.min(1,(c-p)/p)*.08;i.playbackRate*=n;e.log("Our buffer ("+Math.round(c)+"ms) is big (>"+Math.round(p*2)+"ms), so increase the playback speed to "+Math.round(n*100)/100+" to catch up.")}else if(c<0){n=.8;i.playbackRate*=n;e.log("Our buffer ("+Math.round(c)+"ms) is negative so decrease the playback speed to "+Math.round(n*100)/100+" to let it catch up.")}else if(c1){if(cp){i.playbackRate/=n;n=1;e.log("Our buffer ("+Math.round(c)+"ms) is big enough (>"+Math.round(p)+"ms), so return to real time playback.")}}}else{if(n==1){if(r.data.play_rate_curr=="auto"){if(cl){e.log("Our buffer is big, so request a slower download rate.");n=.5;u({type:"set_speed",play_rate:n})}}}else if(n>1){if(c>l){u({type:"set_speed",play_rate:"auto"});n=1;e.log("The buffer is big enough, so ask for realtime download rate.")}}else{if(ct){t=r}if(!i.buffered.length||i.buffered.end(i.buffered.length-1)i.currentTime){var e=i.buffered.start(0);i.currentTime=e;if(i.currentTime!=e){n()}}}else{n()}})};n()}}if(v(a.last_codecs?a.last_codecs:a.sb._codecs,r.data.codecs)){e.log("Player switched tracks, keeping source buffer as codecs are the same as before.");if(i.currentTime==0&&r.data.current!=0){w((r.data.current*.001).toFixed(3))}}else{if(a.debugging){console.warn("Different codecs!");console.warn("video time",i.currentTime,"switch startpoint",r.data.current*.001)}a.last_codecs=r.data.codecs;if(a.msgqueue){a.msgqueue.push([])}else{a.msgqueue=[[]]}var y=function(){if(a&&a.sb){a.sb._do(function(t){if(!a.sb.updating){if(!isNaN(a.ms.duration))a.sb.remove(0,Infinity);a.sb.queue=[];a.ms.removeSourceBuffer(a.sb);a.sb=null;i.src="";a.ms.onsourceclose=null;a.ms.onsourceended=null;if(a.debugging&&t&&t.length){console.warn("There are do_on_updateend functions queued, which I will re-apply after clearing the sb.")}a.msinit().then(function(){a.sbinit(r.data.codecs);a.sb.do_on_updateend=t;var n=MistUtil.event.addListener(i,"loadedmetadata",function(){e.log("Buffer cleared");w((r.data.current*.001).toFixed(3));MistUtil.event.removeListener(n)})})}else{y()}})}else{if(a.debugging){console.warn("sb not available to do clear")}a.onsbinit.push(y)}};if(!r.data.codecs||!r.data.codecs.length){e.showError("Track switch does not contain any codecs, aborting.");e.options.setTracks=false;y();break}function k(t){if(a.debugging){console.warn("reached switching point",t.data.current*.001,MistUtil.format.time(t.data.current*.001))}e.log("Track switch: reached switching point");y()}if(i.currentTime==0){k(r)}else{if(r.data.current>=i.currentTime*1e3){e.log("Track switch: waiting for playback to reach the switching point ("+MistUtil.format.time(r.data.current*.001,{ms:true})+")");var _=MistUtil.event.addListener(i,"timeupdate",function(){if(r.data.current=i.currentTime*1e3){k(e);a.ws.removeListener("on_time",_)}};a.ws.addListener("on_time",_)}}}break}case"pause":{if(a.sb){a.sb.paused=true}break}}if(r.type in this.listeners){for(var g=this.listeners[r.type].length-1;g>=0;g--){this.listeners[r.type][g](r)}}return}var T=new Uint8Array(t.data);if(T){if(a.monitor&&a.monitor.bitCounter){for(var g in a.monitor.bitCounter){a.monitor.bitCounter[g]+=t.data.byteLength*8}}if(a.sb&&!a.msgqueue){if(a.sb.updating||a.sb.queue.length||a.sb._busy){a.sb.queue.push(T)}else{a.sb._append(T)}}else{if(!a.msgqueue){a.msgqueue=[[]]}a.msgqueue[a.msgqueue.length-1].push(T)}}else{e.log("Expecting data from websocket, but received none?!")}};this.ws.serverDelay={delays:[],log:function(e){var t=false;switch(e){case"seek":case"set_speed":{t=e;break}case"request_codec_data":{t="codec_data";break}default:{return}}if(t){var i=(new Date).getTime();function r(){if(!a.ws||!a.ws.serverDelay){return}a.ws.serverDelay.add((new Date).getTime()-i);a.ws.removeListener(t,r)}a.ws.addListener(t,r)}},add:function(e){this.delays.unshift(e);if(this.delays.length>5){this.delays.splice(5)}},get:function(){if(this.delays.length){let e=0;let t=0;for(null;t=3){break}e+=this.delays[t]}return e/t}return 500}}}.bind(this))};this.wsconnect().then(function(){var t=function(e){if(a.ms&&a.ms.readyState=="open"){a.sbinit(e.data.codecs)}else{a.msoninit.push(function(){a.sbinit(e.data.codecs)})}a.ws.removeListener("codec_data",t)};this.ws.addListener("codec_data",t);u({type:"request_codec_data",supported_codecs:e.source.supportedCodecs})}.bind(this));function u(t,i){if(!a.ws){throw"No websocket to send to"}if(i>5){throw"Too many retries, giving up"}if(a.ws.readyState=a.ws.CLOSING){if(e.destroyed){return}e.log("MP4 over WS: reopening websocket");a.wsconnect().then(function(){if(!a.sb){var i=function(e){if(!a.sb){a.sbinit(e.data.codecs)}else{a.api.play().catch(function(){})}a.ws.removeListener("codec_data",i)};a.ws.addListener("codec_data",i);u({type:"request_codec_data",supported_codecs:e.source.supportedCodecs})}else{a.api.play()}u(t)},function(){Mistvideo.error("Lost connection to the Media Server")});return}if(a.debugging){console.log("ws send",t)}a.ws.serverDelay.log(t.type);if(!a.ws.send(JSON.stringify(t))){return u(t,++i)}}a.findBuffer=function(e){var t=false;for(var r=0;r=e){t=r;break}}return t};this.api={play:function(t){return new Promise(function(r,n){if(!i.paused){r();return}if("paused"in a.sb&&!a.sb.paused){i.play().then(r).catch(n);return}var s=function(o){if(!a.sb){e.log("Attempting to play, but the source buffer is being cleared. Waiting for next on_time.");return}if(e.info.type=="live"){if(t||i.currentTime==0){var u=function(){if(i.buffered.length){var t=a.findBuffer(o.data.current*.001);if(t!==false){if(i.buffered.start(t)>i.currentTime||i.buffered.end(t)i.currentTime){a.sb.paused=false;if(i.buffered.length&&i.buffered.start(0)>i.currentTime){i.currentTime=i.buffered.start(0)}i.play().then(r).catch(n);a.ws.removeListener("on_time",s)}};a.ws.addListener("on_time",s);var o={type:"play"};if(t){o.seek_time="live"}u(o)})},pause:function(){i.pause();u({type:"hold"});if(a.sb){a.sb.paused=true}},setTracks:function(e){if(!MistUtil.object.keys(e).length){return}e.type="tracks";e=MistUtil.object.extend({type:"tracks"},e);u(e)},unload:function(){a.api.pause();a.sb._do(function(){a.sb.remove(0,Infinity);try{a.ms.endOfStream()}catch(e){}});a.ws.close()},setSubtitle:function(e){var t=i.getElementsByTagName("track");for(var r=t.length-1;r>=0;r--){i.removeChild(t[r])}if(e){var n=document.createElement("track");i.appendChild(n);n.kind="subtitles";n.label=e.label;n.srclang=e.lang;n.src=e.src;n.setAttribute("default","")}}};Object.defineProperty(this.api,"currentTime",{get:function(){return i.currentTime},set:function(t){if(isNaN(t)||t<0){e.log("Ignoring seek to "+t+" because ewww.");return}MistUtil.event.send("seeking",t,i);u({type:"seek",seek_time:Math.round(Math.max(0,t*1e3-(250+a.ws.serverDelay.get())))});var r=function(n){a.ws.removeListener("seek",r);var s=function(r){a.ws.removeListener("on_time",s);t=r.data.current*.001;t=t.toFixed(3);var n=10;var o=function(){i.currentTime=t;if(i.currentTime.toFixed(3)=0){n--;a.sb._doNext(o)}}};o()};a.ws.addListener("on_time",s)};a.ws.addListener("seek",r);i.currentTime=t;e.log("Seeking to "+MistUtil.format.time(t,{ms:true})+" ("+t+")")}});var d=Infinity;Object.defineProperty(this.api,"duration",{get:function(){return d}});Object.defineProperty(this.api,"playbackRate",{get:function(){return i.playbackRate},set:function(e){var t=function(e){i.playbackRate=e.data.play_rate_curr};a.ws.addListener("set_speed",t);u({type:"set_speed",play_rate:e==1?"auto":e})}});function c(e){Object.defineProperty(a.api,e,{get:function(){return i[e]},set:function(t){return i[e]=t}})}var f=["volume","buffered","muted","loop","paused",,"error","textTracks","webkitDroppedFrameCount","webkitDecodedFrameCount"];for(var n in f){c(f[n])}MistUtil.event.addListener(i,"ended",function(){if(a.api.loop){a.api.currentTime=0;a.sb._do(function(){try{a.sb.remove(0,Infinity)}catch(e){}})}});var l=false;MistUtil.event.addListener(i,"seeking",function(){l=true;var e=MistUtil.event.addListener(i,"seeked",function(){l=false;MistUtil.event.removeListener(e)})});MistUtil.event.addListener(i,"waiting",function(){if(l){return}var t=a.findBuffer(i.currentTime);if(t!==false){if(t+1=i.buffered.start(r)&&i.currentTime<=i.buffered.end(r)){t=true}e.push([i.buffered.start(r),i.buffered.end(r)])}console.log("waiting","currentTime",i.currentTime,"buffers",e,t?"contained":"outside of buffer","readystate",i.readyState,"networkstate",i.networkState);if(i.readyState>=2&&i.networkState>=2){console.error("Why am I waiting?!",i.currentTime)}})}this.ABR={size:null,bitrate:null,generateString:function(e,t){switch(e){case"size":{return"~"+[t.width,t.height].join("x")}case"bitrate":{return"<"+Math.round(t)+"bps,minbps"}default:{throw"Unknown ABR type"}}},request:function(e,t){this[e]=t;var i=[];if(this.bitrate!==null){i.push(this.generateString("bitrate",this.bitrate))}if(this.size!==null){i.push(this.generateString("size",this.size))}else{i.push("maxbps")}return a.api.setTracks({video:i.join(",|")})}};this.api.ABR_resize=function(t){e.log("Requesting the video track with the resolution that best matches the player size");a.ABR.request("size",t)};this.monitor={bitCounter:[],bitsSince:[],currentBps:null,nWaiting:0,nWaitingThreshold:3,listener:e.options.ABR_bitrate?MistUtil.event.addListener(i,"waiting",function(){a.monitor.nWaiting++;if(a.monitor.nWaiting>=a.monitor.nWaitingThreshold){a.monitor.nWaiting=0;a.monitor.action()}}):null,getBitRate:function(){if(a.sb&&!a.sb.paused){this.bitCounter.push(0);this.bitsSince.push((new Date).getTime());var t,i;if(this.bitCounter.length>5){t=a.monitor.bitCounter.shift();i=this.bitsSince.shift()}else{t=a.monitor.bitCounter[0];i=this.bitsSince[0]}var r=(new Date).getTime()-i;this.currentBps=t/(r*.001)}e.timers.start(function(){a.monitor.getBitRate()},500)},action:function(){if(e.options.setTracks&&e.options.setTracks.video){return}e.log("ABR threshold triggered, requesting lower quality");a.ABR.request("bitrate",this.currentBps)}};this.monitor.getBitRate()}; \ No newline at end of file +mistplayers.mews={name:"MSE websocket player",mimes:["ws/video/mp4","ws/video/webm"],priority:MistUtil.object.keys(mistplayers).length+1,isMimeSupported:function(e){return this.mimes.indexOf(e)==-1?false:true},isBrowserSupported:function(e,t,i){if(!("WebSocket"in window)||!("MediaSource"in window)||!("Promise"in window)){return false}if(location.protocol.replace(/^http/,"ws")!=MistUtil.http.url.split(t.url.replace(/^http/,"ws")).protocol){i.log("HTTP/HTTPS mismatch for this source");return false}if(navigator.platform.toUpperCase().indexOf("MAC")>=0){return false}function r(e){if(e.codecstring){return e.codecstring}function t(t){return("0"+e.init.charCodeAt(t).toString(16)).slice(-2)}switch(e.codec){case"AAC":return"mp4a.40.2";case"MP3":return"mp4a.40.34";case"AC3":return"ec-3";case"H264":return"avc1."+t(1)+t(2)+t(3);case"HEVC":return"hev1."+t(1)+t(6)+t(7)+t(8)+t(9)+t(10)+t(11)+t(12);default:return e.codec.toLowerCase()}}var n={};var s={};var a=false;for(var o in i.info.meta.tracks){if(i.info.meta.tracks[o].type!="meta"){n[r(i.info.meta.tracks[o])]=i.info.meta.tracks[o]}else if(i.info.meta.tracks[o].codec=="subtitle"){a=true}}var u=e.split("/")[2];function d(e){return MediaSource.isTypeSupported("video/"+u+';codecs="'+e+'"')}t.supportedCodecs=[];for(var o in n){var c=d(o);if(c){t.supportedCodecs.push(n[o].codec);s[n[o].type]=1}}if(a){for(var o in i.info.source){if(i.info.source[o].type=="html5/text/vtt"){s.subtitle=1;break}}}return MistUtil.object.keys(s)},player:function(){}};var p=mistplayers.mews.player;p.prototype=new MistPlayer;p.prototype.build=function(e,t){var i=document.createElement("video");i.setAttribute("playsinline","");var r=["autoplay","loop","poster"];for(var n in r){var s=r[n];if(e.options[s]){i.setAttribute(s,e.options[s]===true?"":e.options[s])}}if(e.options.muted){i.muted=true}if(e.info.type=="live"){i.loop=false}if(e.options.controls=="stock"){i.setAttribute("controls","")}i.setAttribute("crossorigin","anonymous");this.setSize=function(e){i.style.width=e.width+"px";i.style.height=e.height+"px"};var a=this;a.built=false;function o(){if(a.ws.readyState==a.ws.OPEN&&a.ms.readyState=="open"&&a.sb){if(!a.built){t(i);a.built=true}if(e.options.autoplay){a.api.play().catch((function(){}))}return true}}this.msoninit=[];this.msinit=function(){return new Promise((function(e,t){a.ms=new MediaSource;i.src=URL.createObjectURL(a.ms);a.ms.onsourceopen=function(){for(var t in a.msoninit){a.msoninit[t]()}a.msoninit=[];e()};a.ms.onsourceclose=function(e){if(a.debugging)console.error("ms close",e);u({type:"stop"})};a.ms.onsourceended=function(e){if(a.debugging)console.error("ms ended",e);if(a.debugging=="dl"){function t(e,t,r){var n,s;n=new Blob([e],{type:r});s=window.URL.createObjectURL(n);i(s,t);setTimeout((function(){return window.URL.revokeObjectURL(s)}),1e3)}function i(e,t){var i;i=document.createElement("a");i.href=e;i.download=t;document.body.appendChild(i);i.style="display: none";i.click();i.remove()}var r=0;for(var n=0;n=500){n=0;a.sb._clean(10)}else{n++}var t=r.slice();r=[];for(var s in t){if(!a.sb){if(a.debugging){console.warn("I was doing on_updateend but the sb was reset")}break}if(a.sb.updating){r.concat(t.slice(s));if(a.debugging){console.warn("I was doing on_updateend but was interrupted")}break}t[s](s0&&!a.sb.updating&&!i.error){a.sb._append(this.queue.shift())}}));a.sb.error=function(e){console.error("sb error",e)};a.sb.abort=function(e){console.error("sb abort",e)};a.sb._doNext=function(e){r.push(e)};a.sb._do=function(e){if(this.updating||this._busy){this._doNext(e)}else{e()}};a.sb._append=function(t){if(!t){return}if(!t.buffer){return}if(a.debugging){a.sb.appending=new Uint8Array(t)}if(a.sb._busy){if(a.debugging)console.warn("I wanted to append data, but now I won't because the thingy was still busy. Putting it back in the queue.");a.sb.queue.unshift(t);return}a.sb._busy=true;try{a.sb.appendBuffer(t)}catch(n){switch(n.name){case"QuotaExceededError":{if(i.buffered.length){if(i.currentTime-i.buffered.start(0)>1){e.log("Triggered QuotaExceededError: cleaning up "+Math.round((i.currentTime-i.buffered.start(0)-1)*10)/10+"s");a.sb._clean(1)}else{var r=i.buffered.end(i.buffered.length-1);e.log("Triggered QuotaExceededError but there is nothing to clean: skipping ahead "+Math.round((r-i.currentTime)*10)/10+"s");i.currentTime=r}a.sb._busy=false;a.sb._append(t);return}break}case"InvalidStateError":{a.api.pause();if(e.video.error){return}break}}e.showError(n.message)}};if(a.msgqueue){if(a.msgqueue[0]){var s=false;if(a.msgqueue[0].length){for(var u in a.msgqueue[0]){if(a.sb.updating||a.sb.queue.length||a.sb._busy){a.sb.queue.push(a.msgqueue[0][u])}else{a.sb._append(a.msgqueue[0][u])}}}else{s=true}a.msgqueue.shift();if(a.msgqueue.length==0){a.msgqueue=false}e.log("The newly initialized source buffer was filled with data from a separate message queue."+(a.msgqueue?" "+a.msgqueue.length+" more message queue(s) remain.":""));if(s){e.log("The separate message queue was empty; manually triggering any onupdateend functions");a.sb.dispatchEvent(new Event("updateend"))}}}a.sb._clean=function(e){if(!e)e=180;if(i.currentTime>e){a.sb._do((function(){a.sb.remove(0,Math.max(.1,i.currentTime-e))}))}};if(a.onsbinit.length){a.onsbinit.shift()()}o()};this.wsconnect=function(){return new Promise(function(t,r){this.ws=new WebSocket(e.source.url);this.ws.binaryType="arraybuffer";this.ws.s=this.ws.send;this.ws.send=function(){if(this.readyState==1){this.s.apply(this,arguments);return true}return false};this.ws.onopen=function(){this.wasConnected=true;t()};this.ws.onerror=function(t){e.showError("MP4 over WS: websocket error")};this.ws.onclose=function(t){e.log("MP4 over WS: websocket closed");if(this.wasConnected&&!e.destroyed&&(!a.sb||!a.sb.paused)&&e.state=="Stream is online"&&!(e.video&&e.video.error)){e.log("MP4 over WS: reopening websocket");a.wsconnect().then((function(){if(!a.sb){var t=function(e){if(!a.sb){a.sbinit(e.data.codecs)}else{a.api.play().catch((function(){}))}a.ws.removeListener("codec_data",t)};a.ws.addListener("codec_data",t);u({type:"request_codec_data",supported_codecs:e.source.supportedCodecs})}else{a.api.play()}}),(function(){Mistvideo.error("Lost connection to the Media Server")}))}};this.ws.timeOut=e.timers.start((function(){if(a.ws.readyState==0){e.log("MP4 over WS: socket timeout - try next combo");e.nextCombo()}}),5e3);this.ws.listeners={};this.ws.addListener=function(e,t){if(!(e in this.listeners)){this.listeners[e]=[]}this.listeners[e].push(t)};this.ws.removeListener=function(e,t){if(!(e in this.listeners)){return}var i=this.listeners[e].indexOf(t);if(i<0){return}this.listeners[e].splice(i,1);return true};a.msgqueue=false;var n=1;var s=[];var o=[];this.ws.onmessage=function(t){if(!t.data){throw"Received invalid data"}if(typeof t.data=="string"){var r=JSON.parse(t.data);if(a.debugging&&r.type!="on_time"){console.log("ws message",r)}switch(r.type){case"on_stop":{var s;s=MistUtil.event.addListener(i,"waiting",(function(e){a.sb.paused=true;MistUtil.event.send("ended",null,i);MistUtil.event.removeListener(s)}));a.ws.onclose=function(){};break}case"on_time":{var c=r.data.current-i.currentTime*1e3;var f=a.ws.serverDelay.get();var l=Math.max(100+f,f*2);var p=l+(r.data.jitter?r.data.jitter:0);if(e.info.type!="live"){l+=2e3}if(a.debugging){console.log("on_time received",r.data.current/1e3,"currtime",i.currentTime,n+"x","buffer",Math.round(c),"/",Math.round(l),e.info.type=="live"?"latency:"+Math.round(r.data.end-i.currentTime*1e3)+"ms":"",a.monitor?"bitrate:"+MistUtil.format.bits(a.monitor.currentBps)+"/s":"","listeners",a.ws.listeners&&a.ws.listeners.on_time?a.ws.listeners.on_time:0,"msgqueue",a.msgqueue?a.msgqueue.length:0,"readyState",e.video.readyState,r.data)}if(!a.sb){e.log("Received on_time, but the source buffer is being cleared right now. Ignoring.");break}if(d!=r.data.end*.001){d=r.data.end*.001;MistUtil.event.send("durationchange",null,e.video)}e.info.meta.buffer_window=r.data.end-r.data.begin;a.sb.paused=false;if(e.info.type=="live"){if(n==1){if(r.data.play_rate_curr=="auto"){if(i.currentTime>0){if(c>p*2){n=1+Math.min(1,(c-p)/p)*.08;i.playbackRate*=n;e.log("Our buffer ("+Math.round(c)+"ms) is big (>"+Math.round(p*2)+"ms), so increase the playback speed to "+Math.round(n*100)/100+" to catch up.")}else if(c<0){n=.8;i.playbackRate*=n;e.log("Our buffer ("+Math.round(c)+"ms) is negative so decrease the playback speed to "+Math.round(n*100)/100+" to let it catch up.")}else if(c1){if(cp){i.playbackRate/=n;n=1;e.log("Our buffer ("+Math.round(c)+"ms) is big enough (>"+Math.round(p)+"ms), so return to real time playback.")}}}else{if(n==1){if(r.data.play_rate_curr=="auto"){if(cl){e.log("Our buffer is big, so request a slower download rate.");n=.5;u({type:"set_speed",play_rate:n})}}}else if(n>1){if(c>l){u({type:"set_speed",play_rate:"auto"});n=1;e.log("The buffer is big enough, so ask for realtime download rate.")}}else{if(ct){t=r}if(!i.buffered.length||i.buffered.end(i.buffered.length-1)i.currentTime){var e=i.buffered.start(0);i.currentTime=e;if(i.currentTime!=e){n()}}}else{n()}}))};n()}}if(v(a.last_codecs?a.last_codecs:a.sb._codecs,r.data.codecs)){e.log("Player switched tracks, keeping source buffer as codecs are the same as before.");if(i.currentTime==0&&r.data.current!=0){w((r.data.current*.001).toFixed(3))}}else{if(a.debugging){console.warn("Different codecs!");console.warn("video time",i.currentTime,"switch startpoint",r.data.current*.001)}a.last_codecs=r.data.codecs;if(a.msgqueue){a.msgqueue.push([])}else{a.msgqueue=[[]]}var y=function(){if(a&&a.sb){a.sb._do((function(t){if(!a.sb.updating){if(!isNaN(a.ms.duration))a.sb.remove(0,Infinity);a.sb.queue=[];a.ms.removeSourceBuffer(a.sb);a.sb=null;i.src="";a.ms.onsourceclose=null;a.ms.onsourceended=null;if(a.debugging&&t&&t.length){console.warn("There are do_on_updateend functions queued, which I will re-apply after clearing the sb.")}a.msinit().then((function(){a.sbinit(r.data.codecs);a.sb.do_on_updateend=t;var n=MistUtil.event.addListener(i,"loadedmetadata",(function(){e.log("Buffer cleared");w((r.data.current*.001).toFixed(3));MistUtil.event.removeListener(n)}))}))}else{y()}}))}else{if(a.debugging){console.warn("sb not available to do clear")}a.onsbinit.push(y)}};if(!r.data.codecs||!r.data.codecs.length){e.showError("Track switch does not contain any codecs, aborting.");e.options.setTracks=false;y();break}function k(t){if(a.debugging){console.warn("reached switching point",t.data.current*.001,MistUtil.format.time(t.data.current*.001))}e.log("Track switch: reached switching point");y()}if(i.currentTime==0){k(r)}else{if(r.data.current>=i.currentTime*1e3){e.log("Track switch: waiting for playback to reach the switching point ("+MistUtil.format.time(r.data.current*.001,{ms:true})+")");var _=MistUtil.event.addListener(i,"timeupdate",(function(){if(r.data.current=i.currentTime*1e3){k(e);a.ws.removeListener("on_time",_)}};a.ws.addListener("on_time",_)}}}break}case"pause":{if(a.sb){a.sb.paused=true}break}}if(r.type in this.listeners){for(var g=this.listeners[r.type].length-1;g>=0;g--){this.listeners[r.type][g](r)}}return}var T=new Uint8Array(t.data);if(T){if(a.monitor&&a.monitor.bitCounter){for(var g in a.monitor.bitCounter){a.monitor.bitCounter[g]+=t.data.byteLength*8}}if(a.sb&&!a.msgqueue){if(a.sb.updating||a.sb.queue.length||a.sb._busy){a.sb.queue.push(T)}else{a.sb._append(T)}}else{if(!a.msgqueue){a.msgqueue=[[]]}a.msgqueue[a.msgqueue.length-1].push(T)}}else{e.log("Expecting data from websocket, but received none?!")}};this.ws.serverDelay={delays:[],log:function(e){var t=false;switch(e){case"seek":case"set_speed":{t=e;break}case"request_codec_data":{t="codec_data";break}default:{return}}if(t){var i=(new Date).getTime();function r(){if(!a.ws||!a.ws.serverDelay){return}a.ws.serverDelay.add((new Date).getTime()-i);a.ws.removeListener(t,r)}a.ws.addListener(t,r)}},add:function(e){this.delays.unshift(e);if(this.delays.length>5){this.delays.splice(5)}},get:function(){if(this.delays.length){let e=0;let t=0;for(null;t=3){break}e+=this.delays[t]}return e/t}return 500}}}.bind(this))};this.wsconnect().then(function(){var t=function(e){if(a.ms&&a.ms.readyState=="open"){a.sbinit(e.data.codecs)}else{a.msoninit.push((function(){a.sbinit(e.data.codecs)}))}a.ws.removeListener("codec_data",t)};this.ws.addListener("codec_data",t);u({type:"request_codec_data",supported_codecs:e.source.supportedCodecs})}.bind(this));function u(t,i){if(!a.ws){throw"No websocket to send to"}if(i>5){throw"Too many retries, giving up"}if(a.ws.readyState=a.ws.CLOSING){if(e.destroyed){return}e.log("MP4 over WS: reopening websocket");a.wsconnect().then((function(){if(!a.sb){var i=function(e){if(!a.sb){a.sbinit(e.data.codecs)}else{a.api.play().catch((function(){}))}a.ws.removeListener("codec_data",i)};a.ws.addListener("codec_data",i);u({type:"request_codec_data",supported_codecs:e.source.supportedCodecs})}else{a.api.play()}u(t)}),(function(){Mistvideo.error("Lost connection to the Media Server")}));return}if(a.debugging){console.log("ws send",t)}a.ws.serverDelay.log(t.type);if(!a.ws.send(JSON.stringify(t))){return u(t,++i)}}a.findBuffer=function(e){var t=false;for(var r=0;r=e){t=r;break}}return t};this.api={play:function(t){return new Promise((function(r,n){if(!i.paused){r();return}if("paused"in a.sb&&!a.sb.paused){i.play().then(r).catch(n);return}var s=function(o){if(!a.sb){e.log("Attempting to play, but the source buffer is being cleared. Waiting for next on_time.");return}if(e.info.type=="live"){if(t||i.currentTime==0){var u=function(){if(i.buffered.length){var t=a.findBuffer(o.data.current*.001);if(t!==false){if(i.buffered.start(t)>i.currentTime||i.buffered.end(t)i.currentTime){a.sb.paused=false;if(i.buffered.length&&i.buffered.start(0)>i.currentTime){i.currentTime=i.buffered.start(0)}i.play().then(r).catch(n);a.ws.removeListener("on_time",s)}};a.ws.addListener("on_time",s);var o={type:"play"};if(t){o.seek_time="live"}u(o)}))},pause:function(){i.pause();u({type:"hold"});if(a.sb){a.sb.paused=true}},setTracks:function(e){if(!MistUtil.object.keys(e).length){return}e.type="tracks";e=MistUtil.object.extend({type:"tracks"},e);u(e)},unload:function(){a.api.pause();a.sb._do((function(){a.sb.remove(0,Infinity);try{a.ms.endOfStream()}catch(e){}}));a.ws.close()},setSubtitle:function(e){var t=i.getElementsByTagName("track");for(var r=t.length-1;r>=0;r--){i.removeChild(t[r])}if(e){var n=document.createElement("track");i.appendChild(n);n.kind="subtitles";n.label=e.label;n.srclang=e.lang;n.src=e.src;n.setAttribute("default","")}}};Object.defineProperty(this.api,"currentTime",{get:function(){return i.currentTime},set:function(t){if(isNaN(t)||t<0){e.log("Ignoring seek to "+t+" because ewww.");return}MistUtil.event.send("seeking",t,i);u({type:"seek",seek_time:Math.round(Math.max(0,t*1e3-(250+a.ws.serverDelay.get())))});var r=function(n){a.ws.removeListener("seek",r);var s=function(r){a.ws.removeListener("on_time",s);t=r.data.current*.001;t=t.toFixed(3);var n=10;var o=function(){i.currentTime=t;if(i.currentTime.toFixed(3)=0){n--;a.sb._doNext(o)}}};o()};a.ws.addListener("on_time",s)};a.ws.addListener("seek",r);i.currentTime=t;e.log("Seeking to "+MistUtil.format.time(t,{ms:true})+" ("+t+")")}});var d=Infinity;Object.defineProperty(this.api,"duration",{get:function(){return d}});Object.defineProperty(this.api,"playbackRate",{get:function(){return i.playbackRate},set:function(e){var t=function(e){i.playbackRate=e.data.play_rate_curr};a.ws.addListener("set_speed",t);u({type:"set_speed",play_rate:e==1?"auto":e})}});function c(e){Object.defineProperty(a.api,e,{get:function(){return i[e]},set:function(t){return i[e]=t}})}var f=["volume","buffered","muted","loop","paused",,"error","textTracks","webkitDroppedFrameCount","webkitDecodedFrameCount"];for(var n in f){c(f[n])}MistUtil.event.addListener(i,"ended",(function(){if(a.api.loop){a.api.currentTime=0;a.sb._do((function(){try{a.sb.remove(0,Infinity)}catch(e){}}))}}));var l=false;MistUtil.event.addListener(i,"seeking",(function(){l=true;var e=MistUtil.event.addListener(i,"seeked",(function(){l=false;MistUtil.event.removeListener(e)}))}));MistUtil.event.addListener(i,"waiting",(function(){if(l){return}var t=a.findBuffer(i.currentTime);if(t!==false){if(t+1=i.buffered.start(r)&&i.currentTime<=i.buffered.end(r)){t=true}e.push([i.buffered.start(r),i.buffered.end(r)])}console.log("waiting","currentTime",i.currentTime,"buffers",e,t?"contained":"outside of buffer","readystate",i.readyState,"networkstate",i.networkState);if(i.readyState>=2&&i.networkState>=2){console.error("Why am I waiting?!",i.currentTime)}}))}this.ABR={size:null,bitrate:null,generateString:function(e,t){switch(e){case"size":{return"~"+[t.width,t.height].join("x")}case"bitrate":{return"<"+Math.round(t)+"bps,minbps"}default:{throw"Unknown ABR type"}}},request:function(e,t){this[e]=t;var i=[];if(this.bitrate!==null){i.push(this.generateString("bitrate",this.bitrate))}if(this.size!==null){i.push(this.generateString("size",this.size))}else{i.push("maxbps")}return a.api.setTracks({video:i.join(",|")})}};this.api.ABR_resize=function(t){e.log("Requesting the video track with the resolution that best matches the player size");a.ABR.request("size",t)};this.monitor={bitCounter:[],bitsSince:[],currentBps:null,nWaiting:0,nWaitingThreshold:3,listener:e.options.ABR_bitrate?MistUtil.event.addListener(i,"waiting",(function(){a.monitor.nWaiting++;if(a.monitor.nWaiting>=a.monitor.nWaitingThreshold){a.monitor.nWaiting=0;a.monitor.action()}})):null,getBitRate:function(){if(a.sb&&!a.sb.paused){this.bitCounter.push(0);this.bitsSince.push((new Date).getTime());var t,i;if(this.bitCounter.length>5){t=a.monitor.bitCounter.shift();i=this.bitsSince.shift()}else{t=a.monitor.bitCounter[0];i=this.bitsSince[0]}var r=(new Date).getTime()-i;this.currentBps=t/(r*.001)}e.timers.start((function(){a.monitor.getBitRate()}),500)},action:function(){if(e.options.setTracks&&e.options.setTracks.video){return}e.log("ABR threshold triggered, requesting lower quality");a.ABR.request("bitrate",this.currentBps)}};this.monitor.getBitRate()}; \ No newline at end of file diff --git a/embed/min/wrappers/rawws.js b/embed/min/wrappers/rawws.js index bf19711c..042bc7a6 100644 --- a/embed/min/wrappers/rawws.js +++ b/embed/min/wrappers/rawws.js @@ -1 +1 @@ -mistplayers.rawws={name:"RAW to Canvas",mimes:["ws/video/raw"],priority:MistUtil.object.keys(mistplayers).length+1,isMimeSupported:function(e){return MistUtil.array.indexOf(this.mimes,e)==-1?false:true},isBrowserSupported:function(e,t,r){if(location.protocol!=MistUtil.http.url.split(t.url.replace(/^ws/,"http")).protocol){if(location.protocol=="file:"&&MistUtil.http.url.split(t.url.replace(/^ws/,"http")).protocol=="http:"){r.log("This page was loaded over file://, the player might not behave as intended.")}else{r.log("HTTP/HTTPS mismatch for this source");return false}}for(var i in r.info.meta.tracks){if(r.info.meta.tracks[i].codec=="HEVC"){return["video"]}}return false},player:function(){this.onreadylist=[]},scriptsrc:function(e){return e+"/libde265.js"}};var p=mistplayers.rawws.player;p.prototype=new MistPlayer;p.prototype.build=function(e,t){var r=this;r.onDecoderLoad=function(){if(e.destroyed){return}e.log("Building rawws player..");var i={};e.player.api=i;var a=document.createElement("canvas");var n=a.getContext("2d");a.style.objectFit="contain";r.vars={};if(e.options.autoplay){r.vars.wantToPlay=true}r.dropping=false;r.frames={received:0,bitsReceived:0,decoded:0,dropped:0,behind:function(){return this.received-this.decoded-this.dropped},timestamps:{},frame2time:function(e,t){if(e in this.timestamps){if(t){for(var r in this.timestamps){if(r==e){break}delete this.timestamps[r]}}return this.timestamps[e]*.001}return 0},history:{log:[],add:function(){this.log.unshift({time:(new Date).getTime(),received:r.frames.received,bitsReceived:r.frames.bitsReceived,decoded:r.frames.decoded});if(this.log.length>3){this.log.splice(3)}}},framerate_in:function(){var e=this.history.log.length-1;if(e<1){return 0}var t=this.history.log[0].received-this.history.log[e].received;var r=(this.history.log[0].time-this.history.log[e].time)*.001;return t/r},bitrate_in:function(){var e=this.history.log.length-1;if(e<1){return 0}var t=this.history.log[0].bitsReceived-this.history.log[e].bitsReceived;var r=(this.history.log[0].time-this.history.log[e].time)*.001;return t/r},framerate_out:function(){var e=this.history.log.length-1;if(e<1){return 0}var t=this.history.log[0].decoded-this.history.log[e].decoded;var r=(this.history.log[0].time-this.history.log[e].time)*.001;return t/r},framerate:function(){if("rate_theoretical"in this){return this.rate_theoretical}return this.framerate_in()},keepingUp:function(){var e=this.history.log.length-1;if(e<1){return 0}var t=this.history.log[e].received-this.history.log[e].decoded-(this.history.log[0].received-this.history.log[0].decoded);var r=(this.history.log[0].time-this.history.log[e].time)*.001;var i=t/r;return i/this.framerate()}};i.framerate_in=function(){return r.frames.framerate_in()};i.framerate_out=function(){return r.frames.framerate_out()};i.currentBps=function(){return r.frames.bitrate_in()};i.loop=e.options.loop;Object.defineProperty(e.player.api,"webkitDecodedFrameCount",{get:function(){return r.frames.decoded}});Object.defineProperty(e.player.api,"webkitDroppedFrameCount",{get:function(){return r.frames.dropped}});var s;this.decoder=null;function o(e){MistUtil.event.send(e,undefined,a)}function d(){function i(){s=new libde265.Decoder;e.player.decoder=s;var t=[];s.addListener=function(e){t.push(e)};s.removeListener=function(e){var r=t.indexOf(e);if(r<0){return}t.splice(r,1);return true};var i;if(window.requestAnimationFrame){i=function(e){s.pending_image_data=e;window.requestAnimationFrame(function(){if(s.pending_image_data){n.putImageData(s.pending_image_data,0,0);s.pending_image_data=null}})}}else{i=function(e){n.putImageData(e,0,0)}}s.set_image_callback(function(d){r.frames.decoded++;if(r.vars.wantToPlay&&r.state!="seeking"){o("timeupdate")}if(!s.image_data){var l=d.get_width();var p=d.get_height();if(l!=a.width||p!=a.height||!this.image_data){a.width=l;a.height=p;var f=n.createImageData(l,p);s.image_data=f}}if(r.state!="seeking"){d.display(this.image_data,function(e){s.decoding=false;i(e)})}d.free();switch(r.state){case"play":case"waiting":{if(!r.dropping){o("canplay");o("playing");r.state="playing";if(!r.vars.wantToPlay){e.player.send({type:"hold"})}}break}case"seeking":{var c=r.frames.frame2time(r.frames.decoded+r.frames.dropped);if(c>=r.vars.seekTo){o("seeked");r.vars.seekTo=null;r.state="playing";if(!r.vars.wantToPlay){o("timeupdate");e.player.send({type:"hold"})}}break}default:{r.state="playing"}}for(var u in t){t[u]()}})}i();function d(e){return!!e[1]}function l(e){var t=new DataView(new ArrayBuffer(8));for(var r=0;r<8;r++){t.setUint8(r,e[r+2])}return t.getInt32(4)}function p(){o("loadstart");var i=MistUtil.http.url.addParam(e.source.url,{buffer:0,video:"hevc,|minbps"});var n=new WebSocket(i);e.player.ws=n;n.binaryType="arraybuffer";function f(t){if(!e.player.ws){throw"No websocket to send to"}if(n.readyState==1){n.send(JSON.stringify(t))}return false}e.player.send=f;n.wasConnected=false;n.onopen=function(){if(!e.player.built){e.player.built=true;t(a)}f({type:"request_codec_data",supported_codecs:["HEVC"]});n.wasConnected=true};n.onclose=function(){if(this.wasConnected&&!e.destroyed&&e.state=="Stream is online"){e.log("Raw over WS: reopening websocket");p(i)}else{e.showError("Raw over WS: websocket closed")}};n.onerror=function(t){e.showError("Raw over WS: websocket error")};n.onmessage=function(t){if(typeof t.data=="string"){var i=JSON.parse(t.data);switch(i.type){case"on_time":{r.vars.paused=false;r.frames.history.add();if(r.vars.duration!=i.data.end*.001){r.vars.duration=i.data.end*.001;o("durationchange")}break}case"seek":{e.player.frames.timestamps={};if(e.player.dropping){e.log("Emptying drop queue for seek");e.player.frames.dropped+=e.player.dropping.length;e.player.dropping=[]}break}case"codec_data":{o("loadedmetadata");f({type:"play"});r.state="play";break}case"info":{var a=e.info.meta.tracks;var p;for(var c in a){if(a[c].idx==i.data.tracks[0]){p=a[c];break}}if(typeof p!=undefined&&p.fpks>0){r.frames.rate_theoretical=p.fpks*.001}break}case"pause":{r.vars.paused=i.paused;if(i.paused){r.decoder.flush();o("pause")}break}case"on_stop":{if(r.state=="ended"){return}r.state="ended";r.vars.paused=true;n.onclose=function(){};n.close();r.decoder.flush();o("ended");break}default:{}}}else{r.frames.received++;r.frames.bitsReceived+=t.data.byteLength*8;var u=12;var h=new Uint8Array(t.data.slice(0,u));var m=new Uint8Array(t.data.slice(u,t.data.byteLength));r.frames.timestamps[r.frames.received]=l(h);function y(t,i){setTimeout(function(){if(r.dropping){if(r.state!="waiting"){o("waiting");r.state="waiting"}if(d(i)){if(r.dropping.length){r.frames.dropped+=r.dropping.length;e.log("Dropped "+r.dropping.length+" frames");r.dropping=[]}else{e.log("Caught up! no longer dropping");r.dropping=false}}else{r.dropping.push([i,t]);if(!s.decoding){var a=r.dropping.shift();e.player.process(a[1],a[0])}return}}else{if(r.frames.behind()>20){r.dropping=[];e.log("Falling behind, dropping files..")}}e.player.process(t,i)},0)}y(m,h)}};n.listeners={};n.addListener=function(e,t){if(!(e in this.listeners)){this.listeners[e]=[]}this.listeners[e].push(t)};n.removeListener=function(e,t){if(!(e in this.listeners)){return}var r=this.listeners[e].indexOf(t);if(r<0){return}this.listeners[e].splice(r,1);return true}}e.player.connect=p;e.player.process=function(e,t){s.decoding=true;var i=s.push_data(e);if(r.state=="play"){o("loadeddata");r.state="waiting"}if(r.vars.wantToPlay&&r.state!="seeking"){o("progress")}function n(e){if(e==0){return}if(e==libde265.DE265_ERROR_WAITING_FOR_INPUT_DATA){r.state="waiting";return}if(!libde265.de265_isOK(e)){a.error="Decode error: "+libde265.de265_get_error_text(e);o("error");return true}}if(!n(i)){s.decode(n)}else{s.free()}};p()}d();function l(t){Object.defineProperty(e.player.api,t,{get:function(){return r.vars[t]},set:function(e){return r.vars[t]=e}})}var p=["duration","paused","error"];for(var f in p){l(p[f])}i.play=function(){return new Promise(function(t,a){r.vars.wantToPlay=true;var n=function(){t();e.player.decoder.removeListener(n)};e.player.decoder.addListener(n);if(e.player.ws.readyState>e.player.ws.OPEN){e.player.connect();e.log("Websocket was closed: reconnecting to resume playback");return}if(i.paused)e.player.send({type:"play"});r.state="play"})};i.pause=function(){r.vars.wantToPlay=false;e.player.send({type:"hold"})};e.player.api.unload=function(){if(e.player.ws){e.player.ws.onclose=function(){};e.player.ws.close()}if(e.player.decoder){e.player.decoder.push_data=function(){};e.player.decoder.flush();e.player.decoder.free()}};e.player.setSize=function(e){a.style.width=e.width+"px";a.style.height=e.height+"px"};Object.defineProperty(e.player.api,"currentTime",{get:function(){var e=r.frames.decoded+r.frames.dropped;if(r.state=="seeking"){return r.vars.seekTo}if(e in r.frames.timestamps){return r.frames.frame2time(e)}return 0},set:function(t){o("seeking");r.state="seeking";r.vars.seekTo=t;e.player.send({type:"seek",seek_time:t*1e3});return t}});Object.defineProperty(e.player.api,"buffered",{get:function(){return{start:function(e){if(this.length&&e==0){return r.frames.frame2time(r.frames.decoded+r.frames.dropped)}},end:function(e){if(this.length&&e==0){return r.frames.frame2time(r.frames.received)}},length:r.frames.received-r.frames.decoded>0?1:0}}});if(e.info.type!="live"){MistUtil.event.addListener(a,"ended",function(){if(r.api.loop){r.api.play();r.api.currentTime=0}})}};if("libde265"in window){this.onDecoderLoad()}else{var i=MistUtil.scripts.insert(e.urlappend(mistplayers.rawws.scriptsrc(e.options.host)),{onerror:function(t){var r="Failed to load H265 decoder";if(t.message){r+=": "+t.message}e.showError(r)},onload:e.player.onDecoderLoad},e)}}; \ No newline at end of file +mistplayers.rawws={name:"RAW to Canvas",mimes:["ws/video/raw"],priority:MistUtil.object.keys(mistplayers).length+1,isMimeSupported:function(e){return MistUtil.array.indexOf(this.mimes,e)==-1?false:true},isBrowserSupported:function(e,t,r){if(location.protocol!=MistUtil.http.url.split(t.url.replace(/^ws/,"http")).protocol){if(location.protocol=="file:"&&MistUtil.http.url.split(t.url.replace(/^ws/,"http")).protocol=="http:"){r.log("This page was loaded over file://, the player might not behave as intended.")}else{r.log("HTTP/HTTPS mismatch for this source");return false}}for(var i in r.info.meta.tracks){if(r.info.meta.tracks[i].codec=="HEVC"){return["video"]}}return false},player:function(){this.onreadylist=[]},scriptsrc:function(e){return e+"/libde265.js"}};var p=mistplayers.rawws.player;p.prototype=new MistPlayer;p.prototype.build=function(e,t){var r=this;r.onDecoderLoad=function(){if(e.destroyed){return}e.log("Building rawws player..");var i={};e.player.api=i;var a=document.createElement("canvas");var n=a.getContext("2d");a.style.objectFit="contain";r.vars={};if(e.options.autoplay){r.vars.wantToPlay=true}r.dropping=false;r.frames={received:0,bitsReceived:0,decoded:0,dropped:0,behind:function(){return this.received-this.decoded-this.dropped},timestamps:{},frame2time:function(e,t){if(e in this.timestamps){if(t){for(var r in this.timestamps){if(r==e){break}delete this.timestamps[r]}}return this.timestamps[e]*.001}return 0},history:{log:[],add:function(){this.log.unshift({time:(new Date).getTime(),received:r.frames.received,bitsReceived:r.frames.bitsReceived,decoded:r.frames.decoded});if(this.log.length>3){this.log.splice(3)}}},framerate_in:function(){var e=this.history.log.length-1;if(e<1){return 0}var t=this.history.log[0].received-this.history.log[e].received;var r=(this.history.log[0].time-this.history.log[e].time)*.001;return t/r},bitrate_in:function(){var e=this.history.log.length-1;if(e<1){return 0}var t=this.history.log[0].bitsReceived-this.history.log[e].bitsReceived;var r=(this.history.log[0].time-this.history.log[e].time)*.001;return t/r},framerate_out:function(){var e=this.history.log.length-1;if(e<1){return 0}var t=this.history.log[0].decoded-this.history.log[e].decoded;var r=(this.history.log[0].time-this.history.log[e].time)*.001;return t/r},framerate:function(){if("rate_theoretical"in this){return this.rate_theoretical}return this.framerate_in()},keepingUp:function(){var e=this.history.log.length-1;if(e<1){return 0}var t=this.history.log[e].received-this.history.log[e].decoded-(this.history.log[0].received-this.history.log[0].decoded);var r=(this.history.log[0].time-this.history.log[e].time)*.001;var i=t/r;return i/this.framerate()}};i.framerate_in=function(){return r.frames.framerate_in()};i.framerate_out=function(){return r.frames.framerate_out()};i.currentBps=function(){return r.frames.bitrate_in()};i.loop=e.options.loop;Object.defineProperty(e.player.api,"webkitDecodedFrameCount",{get:function(){return r.frames.decoded}});Object.defineProperty(e.player.api,"webkitDroppedFrameCount",{get:function(){return r.frames.dropped}});var s;this.decoder=null;function o(e){MistUtil.event.send(e,undefined,a)}function d(){function i(){s=new libde265.Decoder;e.player.decoder=s;var t=[];s.addListener=function(e){t.push(e)};s.removeListener=function(e){var r=t.indexOf(e);if(r<0){return}t.splice(r,1);return true};var i;if(window.requestAnimationFrame){i=function(e){s.pending_image_data=e;window.requestAnimationFrame((function(){if(s.pending_image_data){n.putImageData(s.pending_image_data,0,0);s.pending_image_data=null}}))}}else{i=function(e){n.putImageData(e,0,0)}}s.set_image_callback((function(d){r.frames.decoded++;if(r.vars.wantToPlay&&r.state!="seeking"){o("timeupdate")}if(!s.image_data){var l=d.get_width();var p=d.get_height();if(l!=a.width||p!=a.height||!this.image_data){a.width=l;a.height=p;var f=n.createImageData(l,p);s.image_data=f}}if(r.state!="seeking"){d.display(this.image_data,(function(e){s.decoding=false;i(e)}))}d.free();switch(r.state){case"play":case"waiting":{if(!r.dropping){o("canplay");o("playing");r.state="playing";if(!r.vars.wantToPlay){e.player.send({type:"hold"})}}break}case"seeking":{var c=r.frames.frame2time(r.frames.decoded+r.frames.dropped);if(c>=r.vars.seekTo){o("seeked");r.vars.seekTo=null;r.state="playing";if(!r.vars.wantToPlay){o("timeupdate");e.player.send({type:"hold"})}}break}default:{r.state="playing"}}for(var u in t){t[u]()}}))}i();function d(e){return!!e[1]}function l(e){var t=new DataView(new ArrayBuffer(8));for(var r=0;r<8;r++){t.setUint8(r,e[r+2])}return t.getInt32(4)}function p(){o("loadstart");var i=MistUtil.http.url.addParam(e.source.url,{buffer:0,video:"hevc,|minbps"});var n=new WebSocket(i);e.player.ws=n;n.binaryType="arraybuffer";function f(t){if(!e.player.ws){throw"No websocket to send to"}if(n.readyState==1){n.send(JSON.stringify(t))}return false}e.player.send=f;n.wasConnected=false;n.onopen=function(){if(!e.player.built){e.player.built=true;t(a)}f({type:"request_codec_data",supported_codecs:["HEVC"]});n.wasConnected=true};n.onclose=function(){if(this.wasConnected&&!e.destroyed&&e.state=="Stream is online"){e.log("Raw over WS: reopening websocket");p(i)}else{e.showError("Raw over WS: websocket closed")}};n.onerror=function(t){e.showError("Raw over WS: websocket error")};n.onmessage=function(t){if(typeof t.data=="string"){var i=JSON.parse(t.data);switch(i.type){case"on_time":{r.vars.paused=false;r.frames.history.add();if(r.vars.duration!=i.data.end*.001){r.vars.duration=i.data.end*.001;o("durationchange")}break}case"seek":{e.player.frames.timestamps={};if(e.player.dropping){e.log("Emptying drop queue for seek");e.player.frames.dropped+=e.player.dropping.length;e.player.dropping=[]}break}case"codec_data":{o("loadedmetadata");f({type:"play"});r.state="play";break}case"info":{var a=e.info.meta.tracks;var p;for(var c in a){if(a[c].idx==i.data.tracks[0]){p=a[c];break}}if(typeof p!=undefined&&p.fpks>0){r.frames.rate_theoretical=p.fpks*.001}break}case"pause":{r.vars.paused=i.paused;if(i.paused){r.decoder.flush();o("pause")}break}case"on_stop":{if(r.state=="ended"){return}r.state="ended";r.vars.paused=true;n.onclose=function(){};n.close();r.decoder.flush();o("ended");break}default:{}}}else{r.frames.received++;r.frames.bitsReceived+=t.data.byteLength*8;var u=12;var h=new Uint8Array(t.data.slice(0,u));var m=new Uint8Array(t.data.slice(u,t.data.byteLength));r.frames.timestamps[r.frames.received]=l(h);function y(t,i){setTimeout((function(){if(r.dropping){if(r.state!="waiting"){o("waiting");r.state="waiting"}if(d(i)){if(r.dropping.length){r.frames.dropped+=r.dropping.length;e.log("Dropped "+r.dropping.length+" frames");r.dropping=[]}else{e.log("Caught up! no longer dropping");r.dropping=false}}else{r.dropping.push([i,t]);if(!s.decoding){var a=r.dropping.shift();e.player.process(a[1],a[0])}return}}else{if(r.frames.behind()>20){r.dropping=[];e.log("Falling behind, dropping files..")}}e.player.process(t,i)}),0)}y(m,h)}};n.listeners={};n.addListener=function(e,t){if(!(e in this.listeners)){this.listeners[e]=[]}this.listeners[e].push(t)};n.removeListener=function(e,t){if(!(e in this.listeners)){return}var r=this.listeners[e].indexOf(t);if(r<0){return}this.listeners[e].splice(r,1);return true}}e.player.connect=p;e.player.process=function(e,t){s.decoding=true;var i=s.push_data(e);if(r.state=="play"){o("loadeddata");r.state="waiting"}if(r.vars.wantToPlay&&r.state!="seeking"){o("progress")}function n(e){if(e==0){return}if(e==libde265.DE265_ERROR_WAITING_FOR_INPUT_DATA){r.state="waiting";return}if(!libde265.de265_isOK(e)){a.error="Decode error: "+libde265.de265_get_error_text(e);o("error");return true}}if(!n(i)){s.decode(n)}else{s.free()}};p()}d();function l(t){Object.defineProperty(e.player.api,t,{get:function(){return r.vars[t]},set:function(e){return r.vars[t]=e}})}var p=["duration","paused","error"];for(var f in p){l(p[f])}i.play=function(){return new Promise((function(t,a){r.vars.wantToPlay=true;var n=function(){t();e.player.decoder.removeListener(n)};e.player.decoder.addListener(n);if(e.player.ws.readyState>e.player.ws.OPEN){e.player.connect();e.log("Websocket was closed: reconnecting to resume playback");return}if(i.paused)e.player.send({type:"play"});r.state="play"}))};i.pause=function(){r.vars.wantToPlay=false;e.player.send({type:"hold"})};e.player.api.unload=function(){if(e.player.ws){e.player.ws.onclose=function(){};e.player.ws.close()}if(e.player.decoder){e.player.decoder.push_data=function(){};e.player.decoder.flush();e.player.decoder.free()}};e.player.setSize=function(e){a.style.width=e.width+"px";a.style.height=e.height+"px"};Object.defineProperty(e.player.api,"currentTime",{get:function(){var e=r.frames.decoded+r.frames.dropped;if(r.state=="seeking"){return r.vars.seekTo}if(e in r.frames.timestamps){return r.frames.frame2time(e)}return 0},set:function(t){o("seeking");r.state="seeking";r.vars.seekTo=t;e.player.send({type:"seek",seek_time:t*1e3});return t}});Object.defineProperty(e.player.api,"buffered",{get:function(){return{start:function(e){if(this.length&&e==0){return r.frames.frame2time(r.frames.decoded+r.frames.dropped)}},end:function(e){if(this.length&&e==0){return r.frames.frame2time(r.frames.received)}},length:r.frames.received-r.frames.decoded>0?1:0}}});if(e.info.type!="live"){MistUtil.event.addListener(a,"ended",(function(){if(r.api.loop){r.api.play();r.api.currentTime=0}}))}};if("libde265"in window){this.onDecoderLoad()}else{var i=MistUtil.scripts.insert(e.urlappend(mistplayers.rawws.scriptsrc(e.options.host)),{onerror:function(t){var r="Failed to load H265 decoder";if(t.message){r+=": "+t.message}e.showError(r)},onload:e.player.onDecoderLoad},e)}}; \ No newline at end of file diff --git a/embed/min/wrappers/videojs.js b/embed/min/wrappers/videojs.js index 94fde146..94d5d939 100644 --- a/embed/min/wrappers/videojs.js +++ b/embed/min/wrappers/videojs.js @@ -1 +1 @@ -mistplayers.videojs={name:"VideoJS player",mimes:["html5/application/vnd.apple.mpegurl","html5/application/vnd.apple.mpegurl;version=7"],priority:MistUtil.object.keys(mistplayers).length+1,isMimeSupported:function(e){return MistUtil.array.indexOf(this.mimes,e)==-1?false:true},isBrowserSupported:function(e,t,r){if(location.protocol!=MistUtil.http.url.split(t.url).protocol){r.log("HTTP/HTTPS mismatch for this source");return false}if(location.protocol=="file:"&&e=="html5/application/vnd.apple"){r.log("This source ("+e+") won't load if the page is run via file://");return false}function i(e){if(!MediaSource.isTypeSupported){return true}var t={};var i=false;for(var o in r.info.meta.tracks){if(r.info.meta.tracks[o].type=="meta"){if(r.info.meta.tracks[o].codec=="subtitle"){i=true}continue}if(!(r.info.meta.tracks[o].type in t)){t[r.info.meta.tracks[o].type]={}}t[r.info.meta.tracks[o].type][MistUtil.tracks.translateCodec(r.info.meta.tracks[o])]=1}var s=[];for(var a in t){var n=false;for(var l in t[a]){if(MediaSource.isTypeSupported(e+';codecs="'+l+'"')){n=true;break}}if(n){s.push(a)}}if(i){for(var o in r.info.source){if(r.info.source[o].type=="html5/text/vtt"){s.push("subtitle");break}}}return s.length?s:false}if(document.createElement("video").canPlayType(e.replace("html5/",""))){if(!("MediaSource"in window)){return true}if(!MediaSource.isTypeSupported){return true}return i(e.replace("html5/",""))}if(!("MediaSource"in window)){return false}return i("video/mp4")},player:function(){},scriptsrc:function(e){return e+"/videojs.js"}};var p=mistplayers.videojs.player;p.prototype=new MistPlayer;p.prototype.build=function(e,t){var r=this;var i;function o(){if(e.destroyed){return}e.log("Building VideoJS player..");i=document.createElement("video");if(e.source.type!="html5/video/ogg"){i.crossOrigin="anonymous"}i.setAttribute("playsinline","");var o=e.source.type.split("/");if(o[0]=="html5"){o.shift()}var s=document.createElement("source");s.setAttribute("src",e.source.url);r.source=s;i.appendChild(s);s.type=o.join("/");e.log("Adding "+s.type+" source @ "+e.source.url);MistUtil.class.add(i,"video-js");var a={};if(e.options.autoplay){a.autoplay=true}if(e.options.loop&&e.info.type!="live"){i.setAttribute("loop","")}if(e.options.muted){i.setAttribute("muted","")}if(e.options.poster){a.poster=e.options.poster}if(e.options.controls=="stock"){i.setAttribute("controls","");if(!document.getElementById("videojs-css")){var n=document.createElement("link");n.rel="stylesheet";n.href=e.options.host+"/skins/videojs.css";n.id="videojs-css";document.head.appendChild(n)}}else{a.controls=false}var l=MistUtil.event.addListener(i,"error",function(t){t.stopImmediatePropagation();var r=t.message;if(!r&&i.error){if("code"in i.error&&i.error.code){r="Code "+i.error.code;for(var o in i.error){if(o=="code"){continue}if(i.error[o]==i.error.code){r=o;break}}}else{r=JSON.stringify(i.error)}}e.log("Error captured and stopped because videojs has not yet loaded: "+r)});function d(){var e=navigator.userAgent.toLowerCase().match(/android\s([\d\.]*)/i);return e?e[1]:false}var p=MistUtil.getAndroid();if(p&&parseFloat(p)<7){e.log("Detected android < 7: instructing videojs to override native playback");a.html5={hls:{overrideNative:true}};a.nativeAudioTracks=false;a.nativeVideoTracks=false}r.onready(function(){e.log("Building videojs");r.videojs=videojs(i,a,function(){MistUtil.event.removeListener(l);e.log("Videojs initialized");if(e.info.type=="live"){MistUtil.event.addListener(i,"progress",function(t){var r=e.player.videojs.seekable().length-1;e.info.meta.buffer_window=(Math.max(e.player.videojs.seekable().end(r),i.duration)-e.player.videojs.seekable().start(r))*1e3})}});MistUtil.event.addListener(i,"error",function(t){if(t&&t.target&&t.target.error&&t.target.error.message&&MistUtil.array.indexOf(t.target.error.message,"NS_ERROR_DOM_MEDIA_OVERFLOW_ERR")>=0){e.timers.start(function(){e.log("Reloading player because of NS_ERROR_DOM_MEDIA_OVERFLOW_ERR");e.reload()},1e3)}});r.api.unload=function(){if(r.videojs){r.videojs.autoplay(false);r.videojs.pause();r.videojs.dispose();r.videojs=false;e.log("Videojs instance disposed")}}});e.log("Built html");if("Proxy"in window&&"Reflect"in window){var u={get:{},set:{}};e.player.api=new Proxy(i,{get:function(e,t,r){if(t in u.get){return u.get[t].apply(e,arguments)}var i=e[t];if(typeof i==="function"){return function(){return i.apply(e,arguments)}}return i},set:function(e,t,r){if(t in u.set){return u.set[t].call(e,r)}return e[t]=r}});e.player.api.load=function(){};u.set.currentTime=function(t){e.player.videojs.currentTime(t)};var f=0;var c=Infinity;for(var v in e.info.meta.tracks){f=Math.max(f,e.info.meta.tracks[v].lastms);c=Math.min(c,e.info.meta.tracks[v].firstms)}var y=c*.001;u.get.duration=function(){if(e.info){var t=i.duration;return t+y}return 0};MistUtil.event.addListener(i,"progress",function(){e.player.api.lastProgress=new Date});u.set.currentTime=function(t){var r=e.player.api.currentTime-t;var i=t-e.player.api.duration;e.log("Seeking to "+MistUtil.format.time(t)+" ("+Math.round(i*-10)/10+"s from live)");e.player.videojs.currentTime(e.video.currentTime-r)};u.get.currentTime=function(){var t=e.player.videojs?e.player.videojs.currentTime():i.currentTime;if(isNaN(t)){return 0}return t+y};u.get.buffered=function(){var t=e.player.videojs?e.player.videojs.buffered():i.buffered;return{length:t.length,start:function(e){return t.start(e)+y},end:function(e){return t.end(e)+y}}};if(e.info.type=="live"){e.player.api.lastProgress=new Date;e.player.api.liveOffset=0}}else{r.api=i}e.player.setSize=function(t){if("videojs"in e.player){e.player.videojs.dimensions(t.width,t.height);i.parentNode.style.width=t.width+"px";i.parentNode.style.height=t.height+"px"}this.api.style.width=t.width+"px";this.api.style.height=t.height+"px"};e.player.api.setSource=function(t){if(!e.player.videojs){return}if(e.player.videojs.src()!=t){e.player.videojs.src({type:e.player.videojs.currentSource().type,src:t})}};e.player.api.setSubtitle=function(e){var t=i.getElementsByTagName("track");for(var r=t.length-1;r>=0;r--){i.removeChild(t[r])}if(e){var o=document.createElement("track");i.appendChild(o);o.kind="subtitles";o.label=e.label;o.srclang=e.lang;o.src=e.src;o.setAttribute("default","")}};if(e.info.type=="live"){var m=MistUtil.event.addListener(i,"loadstart",function(e){MistUtil.event.removeListener(m);MistUtil.event.send("canplay",false,this)});var g=MistUtil.event.addListener(i,"canplay",function(e){if(m){MistUtil.event.removeListener(m)}MistUtil.event.removeListener(g)})}t(i)}if("videojs"in window){o()}else{var s=false;function a(){try{e.video.pause()}catch(e){}e.showError("Error in videojs player");if(!window.mistplayer_videojs_failures){window.mistplayer_videojs_failures=1;e.reload()}else{if(!s){var t=.05*Math.pow(2,window.mistplayer_videojs_failures);e.log("Rate limiter activated: MistPlayer reload delayed by "+Math.round(t*10)/10+" seconds.","error");s=e.timers.start(function(){s=false;delete window.videojs;e.reload()},t*1e3);window.mistplayer_videojs_failures++}}}var n=e.urlappend(mistplayers.videojs.scriptsrc(e.options.host));var l;var d=function(e,t,r,i,o){if(!l){return}if(t==l.src){window.removeEventListener("error",d);a()}return false};window.addEventListener("error",d);l=MistUtil.scripts.insert(n,{onerror:function(t){var r="Failed to load videojs.js";if(t.message){r+=": "+t.message}e.showError(r)},onload:o},e)}}; \ No newline at end of file +mistplayers.videojs={name:"VideoJS player",mimes:["html5/application/vnd.apple.mpegurl","html5/application/vnd.apple.mpegurl;version=7"],priority:MistUtil.object.keys(mistplayers).length+1,isMimeSupported:function(e){return MistUtil.array.indexOf(this.mimes,e)==-1?false:true},isBrowserSupported:function(e,t,r){if(location.protocol!=MistUtil.http.url.split(t.url).protocol){r.log("HTTP/HTTPS mismatch for this source");return false}if(location.protocol=="file:"&&e=="html5/application/vnd.apple"){r.log("This source ("+e+") won't load if the page is run via file://");return false}function i(e){if(!MediaSource.isTypeSupported){return true}var t={};var i=false;for(var o in r.info.meta.tracks){if(r.info.meta.tracks[o].type=="meta"){if(r.info.meta.tracks[o].codec=="subtitle"){i=true}continue}if(!(r.info.meta.tracks[o].type in t)){t[r.info.meta.tracks[o].type]={}}t[r.info.meta.tracks[o].type][MistUtil.tracks.translateCodec(r.info.meta.tracks[o])]=1}var s=[];for(var a in t){var n=false;for(var l in t[a]){if(MediaSource.isTypeSupported(e+';codecs="'+l+'"')){n=true;break}}if(n){s.push(a)}}if(i){for(var o in r.info.source){if(r.info.source[o].type=="html5/text/vtt"){s.push("subtitle");break}}}return s.length?s:false}if(document.createElement("video").canPlayType(e.replace("html5/",""))){if(!("MediaSource"in window)){return true}if(!MediaSource.isTypeSupported){return true}return i(e.replace("html5/",""))}if(!("MediaSource"in window)){return false}return i("video/mp4")},player:function(){},scriptsrc:function(e){return e+"/videojs.js"}};var p=mistplayers.videojs.player;p.prototype=new MistPlayer;p.prototype.build=function(e,t){var r=this;var i;function o(){if(e.destroyed){return}e.log("Building VideoJS player..");i=document.createElement("video");if(e.source.type!="html5/video/ogg"){i.crossOrigin="anonymous"}i.setAttribute("playsinline","");var o=e.source.type.split("/");if(o[0]=="html5"){o.shift()}var s=document.createElement("source");s.setAttribute("src",e.source.url);r.source=s;i.appendChild(s);s.type=o.join("/");e.log("Adding "+s.type+" source @ "+e.source.url);MistUtil.class.add(i,"video-js");var a={};if(e.options.autoplay){a.autoplay=true}if(e.options.loop&&e.info.type!="live"){i.setAttribute("loop","")}if(e.options.muted){i.setAttribute("muted","")}if(e.options.poster){a.poster=e.options.poster}if(e.options.controls=="stock"){i.setAttribute("controls","");if(!document.getElementById("videojs-css")){var n=document.createElement("link");n.rel="stylesheet";n.href=e.options.host+"/skins/videojs.css";n.id="videojs-css";document.head.appendChild(n)}}else{a.controls=false}var l=MistUtil.event.addListener(i,"error",(function(t){t.stopImmediatePropagation();var r=t.message;if(!r&&i.error){if("code"in i.error&&i.error.code){r="Code "+i.error.code;for(var o in i.error){if(o=="code"){continue}if(i.error[o]==i.error.code){r=o;break}}}else{r=JSON.stringify(i.error)}}e.log("Error captured and stopped because videojs has not yet loaded: "+r)}));function d(){var e=navigator.userAgent.toLowerCase().match(/android\s([\d\.]*)/i);return e?e[1]:false}var p=MistUtil.getAndroid();if(p&&parseFloat(p)<7){e.log("Detected android < 7: instructing videojs to override native playback");a.html5={hls:{overrideNative:true}};a.nativeAudioTracks=false;a.nativeVideoTracks=false}r.onready((function(){e.log("Building videojs");r.videojs=videojs(i,a,(function(){MistUtil.event.removeListener(l);e.log("Videojs initialized");if(e.info.type=="live"){MistUtil.event.addListener(i,"progress",(function(t){var r=e.player.videojs.seekable().length-1;e.info.meta.buffer_window=(Math.max(e.player.videojs.seekable().end(r),i.duration)-e.player.videojs.seekable().start(r))*1e3}))}}));MistUtil.event.addListener(i,"error",(function(t){if(t&&t.target&&t.target.error&&t.target.error.message&&MistUtil.array.indexOf(t.target.error.message,"NS_ERROR_DOM_MEDIA_OVERFLOW_ERR")>=0){e.timers.start((function(){e.log("Reloading player because of NS_ERROR_DOM_MEDIA_OVERFLOW_ERR");e.reload()}),1e3)}}));r.api.unload=function(){if(r.videojs){r.videojs.autoplay(false);r.videojs.pause();r.videojs.dispose();r.videojs=false;e.log("Videojs instance disposed")}}}));e.log("Built html");if("Proxy"in window&&"Reflect"in window){var u={get:{},set:{}};e.player.api=new Proxy(i,{get:function(e,t,r){if(t in u.get){return u.get[t].apply(e,arguments)}var i=e[t];if(typeof i==="function"){return function(){return i.apply(e,arguments)}}return i},set:function(e,t,r){if(t in u.set){return u.set[t].call(e,r)}return e[t]=r}});e.player.api.load=function(){};u.set.currentTime=function(t){e.player.videojs.currentTime(t)};var f=0;var c=Infinity;for(var v in e.info.meta.tracks){f=Math.max(f,e.info.meta.tracks[v].lastms);c=Math.min(c,e.info.meta.tracks[v].firstms)}var y=c*.001;u.get.duration=function(){if(e.info){var t=i.duration;return t+y}return 0};MistUtil.event.addListener(i,"progress",(function(){e.player.api.lastProgress=new Date}));u.set.currentTime=function(t){var r=e.player.api.currentTime-t;var i=t-e.player.api.duration;e.log("Seeking to "+MistUtil.format.time(t)+" ("+Math.round(i*-10)/10+"s from live)");e.player.videojs.currentTime(e.video.currentTime-r)};u.get.currentTime=function(){var t=e.player.videojs?e.player.videojs.currentTime():i.currentTime;if(isNaN(t)){return 0}return t+y};u.get.buffered=function(){var t=e.player.videojs?e.player.videojs.buffered():i.buffered;return{length:t.length,start:function(e){return t.start(e)+y},end:function(e){return t.end(e)+y}}};if(e.info.type=="live"){e.player.api.lastProgress=new Date;e.player.api.liveOffset=0}}else{r.api=i}e.player.setSize=function(t){if("videojs"in e.player){e.player.videojs.dimensions(t.width,t.height);i.parentNode.style.width=t.width+"px";i.parentNode.style.height=t.height+"px"}this.api.style.width=t.width+"px";this.api.style.height=t.height+"px"};e.player.api.setSource=function(t){if(!e.player.videojs){return}if(e.player.videojs.src()!=t){e.player.videojs.src({type:e.player.videojs.currentSource().type,src:t})}};e.player.api.setSubtitle=function(e){var t=i.getElementsByTagName("track");for(var r=t.length-1;r>=0;r--){i.removeChild(t[r])}if(e){var o=document.createElement("track");i.appendChild(o);o.kind="subtitles";o.label=e.label;o.srclang=e.lang;o.src=e.src;o.setAttribute("default","")}};if(e.info.type=="live"){var m=MistUtil.event.addListener(i,"loadstart",(function(e){MistUtil.event.removeListener(m);MistUtil.event.send("canplay",false,this)}));var g=MistUtil.event.addListener(i,"canplay",(function(e){if(m){MistUtil.event.removeListener(m)}MistUtil.event.removeListener(g)}))}t(i)}if("videojs"in window){o()}else{var s=false;function a(){try{e.video.pause()}catch(e){}e.showError("Error in videojs player");if(!window.mistplayer_videojs_failures){window.mistplayer_videojs_failures=1;e.reload()}else{if(!s){var t=.05*Math.pow(2,window.mistplayer_videojs_failures);e.log("Rate limiter activated: MistPlayer reload delayed by "+Math.round(t*10)/10+" seconds.","error");s=e.timers.start((function(){s=false;delete window.videojs;e.reload()}),t*1e3);window.mistplayer_videojs_failures++}}}var n=e.urlappend(mistplayers.videojs.scriptsrc(e.options.host));var l;var d=function(e,t,r,i,o){if(!l){return}if(t==l.src){window.removeEventListener("error",d);a()}return false};window.addEventListener("error",d);l=MistUtil.scripts.insert(n,{onerror:function(t){var r="Failed to load videojs.js";if(t.message){r+=": "+t.message}e.showError(r)},onload:o},e)}}; \ No newline at end of file diff --git a/embed/min/wrappers/webrtc.js b/embed/min/wrappers/webrtc.js index 916e8cd6..7eb8dc20 100644 --- a/embed/min/wrappers/webrtc.js +++ b/embed/min/wrappers/webrtc.js @@ -1 +1 @@ -mistplayers.webrtc={name:"WebRTC player",mimes:["webrtc"],priority:MistUtil.object.keys(mistplayers).length+1,isMimeSupported:function(e){return this.mimes.indexOf(e)==-1?false:true},isBrowserSupported:function(e,t,n){if(!("WebSocket"in window)||(!("RTCPeerConnection"in window)||!("RTCRtpReceiver"in window))){return false}if(location.protocol.replace(/^http/,"ws")!=MistUtil.http.url.split(t.url.replace(/^http/,"ws")).protocol){n.log("HTTP/HTTPS mismatch for this source");return false}var i={};var r=false;for(var s in n.info.meta.tracks){if(n.info.meta.tracks[s].type=="meta"){if(n.info.meta.tracks[s].codec=="subtitle"){r=true}continue}if(!(n.info.meta.tracks[s].type in i)){i[n.info.meta.tracks[s].type]={}}i[n.info.meta.tracks[s].type][n.info.meta.tracks[s].codec]=1}var o=[];for(var a in i){var c=false;for(var l in i[a]){var f=RTCRtpReceiver.getCapabilities(a).codecs;for(var s in f){if(f[s].mimeType.toLowerCase()==(a+"/"+l).toLowerCase()){c=true;break}}}if(c){o.push(a)}}if(r){for(var s in n.info.source){if(n.info.source[s].type=="html5/text/vtt"){o.push("subtitle");break}}}return o.length?o:false},player:function(){}};var p=mistplayers.webrtc.player;p.prototype=new MistPlayer;p.prototype.build=function(e,t){var n=this;if(typeof WebRTCBrowserEqualizerLoaded=="undefined"||!WebRTCBrowserEqualizerLoaded){var i=document.createElement("script");i.src=e.urlappend(e.options.host+"/webrtc.js");e.log("Retrieving webRTC browser equalizer code from "+i.src);document.head.appendChild(i);i.onerror=function(){e.showError("Failed to load webrtc browser equalizer",{nextCombo:5})};i.onload=function(){n.build(e,t)};return}var r=document.createElement("video");r.setAttribute("playsinline","");var s=["autoplay","loop","poster"];for(var o in s){var a=s[o];if(e.options[a]){r.setAttribute(a,e.options[a]===true?"":e.options[a])}}if(e.options.muted){r.muted=true}if(e.info.type=="live"){r.loop=false}if(e.options.controls=="stock"){r.setAttribute("controls","")}r.setAttribute("crossorigin","anonymous");this.setSize=function(e){r.style.width=e.width+"px";r.style.height=e.height+"px"};MistUtil.event.addListener(r,"loadeddata",b);MistUtil.event.addListener(r,"seeked",b);if(!e.options.autoplay){MistUtil.event.addListener(r,"canplay",function(){var t=MistUtil.event.addListener(r,"play",function(){e.log("Pausing because autoplay is disabled");var i=MistUtil.event.addListener(r,"pause",function(){e.options.autoplay=false;MistUtil.event.removeListener(i)});n.api.pause();MistUtil.event.removeListener(t)})})}var c=0;var l=false;var f=[];this.listeners={on_connected:function(){c=0;l=false;this.webrtc.play();MistUtil.event.send("webrtc_connected",null,r)},on_disconnected:function(){MistUtil.event.send("webrtc_disconnected",null,r);e.log("Websocket sent on_disconnect");if(!l){r.pause()}},on_answer_sdp:function(t){if(!t.result){e.showError("Failed to open stream.");this.on_disconnected();return}e.log("SDP answer received")},on_time:function(t){var n=c;c=t.current*.001-r.currentTime;if(Math.abs(n-c)>1){b()}if((!("paused"in t)||!t.paused)&&r.paused){r.play()}var i=t.end==0?Infinity:t.end*.001;if(i!=p){p=i;MistUtil.event.send("durationchange",i,r)}e.info.meta.buffer_window=t.end-t.begin;if(t.tracks&&f!=t.tracks){var s=e.info?MistUtil.tracks.parse(e.info.meta.tracks):[];for(var o in t.tracks){if(f.indexOf(t.tracks[o])<0){var a;for(var l in s){if(t.tracks[o]in s[l]){a=l;break}}if(!a){continue}if(a=="subtitle"){continue}MistUtil.event.send("playerUpdate_trackChanged",{type:a,trackid:t.tracks[o]},e.video)}}f=t.tracks}if(e.reporting&&t.tracks){e.reporting.stats.d.tracks=t.tracks.join(",")}},seek:function(e){var t=this;MistUtil.event.send("seeked",c,r);if(e.live_point){t.webrtc.playbackrate("auto")}if("seekPromise"in this.webrtc.signaling){r.play().then(function(){if("seekPromise"in t.webrtc.signaling){t.webrtc.signaling.seekPromise.resolve("Play promise resolved")}}).catch(function(){if("seekPromise"in t.webrtc.signaling){t.webrtc.signaling.seekPromise.reject("Play promise rejected")}})}else{r.play()}},set_speed:function(e){this.webrtc.play_rate=e.play_rate_curr;MistUtil.event.send("ratechange",e,r)},on_stop:function(){e.log("Websocket sent on_stop");r.pause();MistUtil.event.send("ended",null,r);l=true}};function u(){this.peerConn=null;this.localOffer=null;this.isConnected=false;this.isConnecting=false;this.play_rate="auto";var t=this;this.on_event=function(i){switch(i.type){case"on_connected":{t.isConnected=true;t.isConnecting=false;break}case"on_answer_sdp":{t.peerConn.setRemoteDescription({type:"answer",sdp:i.answer_sdp}).then(function(){},function(e){console.error(e)});break}case"on_disconnected":{t.isConnected=false;break}case"on_error":{e.showError("WebRTC error: "+MistUtil.format.ucFirst(i.message));return;break}}if(i.type in n.listeners){return n.listeners[i.type].call(n,"data"in i?i.data:i)}e.log("Unhandled WebRTC event "+i.type+": "+JSON.stringify(i));return false};this.connect=function(n){t.isConnecting=true;e.container.setAttribute("data-loading","connecting");function i(e){var t=new Promise(function(t,n){function i(e){try{var r=RTCRtpReceiver.getCapabilities("video");for(var s=0;s0){setTimeout(function(){i(e-1)},100)}else{n("H264 not found :(")}}catch(e){t("Checker unavailable")}}i(e)});return t}i(5).catch(function(){e.log("Beware: this device does not seem to be able to play H264.")}).finally(function(){t.signaling=new d(t.on_event);var i={};if(e.options.RTCIceServers){i.iceServers=e.options.RTCIceServers}else if(e.source.RTCIceServers){i.iceServers=e.source.RTCIceServers}t.peerConn=new RTCPeerConnection(i);t.MetaDataTrack=t.peerConn.createDataChannel("*",{protocol:"JSON"});t.peerConn.ontrack=function(e){r.srcObject=e.streams[0];if(n){n()}};t.peerConn.ondatachannel=function(){console.warn("ondatachannel",arguments)};t.peerConn.onconnectionstatechange=function(t){if(e.destroyed){return}switch(this.connectionState){case"failed":{e.log("UDP connection failed, trying next combo.","error");e.nextCombo();break}case"connected":{e.container.removeAttribute("data-loading")}case"disconnected":case"closed":case"new":case"connecting":default:{e.log("WebRTC connection state changed to "+this.connectionState);break}}};t.peerConn.oniceconnectionstatechange=function(t){if(e.destroyed){return}switch(this.iceConnectionState){case"failed":{e.showError("ICE connection "+this.iceConnectionState);break}case"disconnected":case"closed":case"new":case"checking":case"connected":case"completed":default:{e.log("WebRTC ICE connection state changed to "+this.iceConnectionState);break}}};MistUtil.event.send("webrtc_ready",null,r)})};this.play=function(){if(!this.isConnected){throw"Not connected, cannot play"}this.peerConn.createOffer({offerToReceiveAudio:true,offerToReceiveVideo:true}).then(function(e){t.localOffer=e;t.peerConn.setLocalDescription(e).then(function(){t.signaling.sendOfferSDP(t.localOffer.sdp)},function(e){console.error(e)})},function(e){throw e})};this.stop=function(){if(!this.isConnected){throw"Not connected, cannot stop."}this.signaling.send({type:"stop"})};this.seek=function(n){var i=new Promise(function(i,r){if(!t.isConnected||!t.signaling){if(t.isConnecting){var s=MistUtil.event.addListener(e.video,"loadstart",function(){t.seek(n);MistUtil.event.removeListener(s)});return r("Not connected yet, will seek once connected")}else{return r("Failed seek: not connected")}}t.signaling.send({type:"seek",seek_time:n=="live"?"live":n*1e3});if("seekPromise"in t.signaling){t.signaling.seekPromise.reject("Doing new seek")}t.signaling.seekPromise={resolve:function(e){i("seeked");delete t.signaling.seekPromise},reject:function(e){r("Failed to seek: "+e);delete t.signaling.seekPromise}}});return i};this.pause=function(){if(!this.isConnected){throw"Not connected, cannot pause."}this.signaling.send({type:"hold"})};this.setTrack=function(e){if(!this.isConnected){throw"Not connected, cannot set track."}e.type="tracks";this.signaling.send(e)};this.playbackrate=function(e){if(typeof e=="undefined"){return n.webrtc.play_rate=="auto"?1:n.webrtc.play_rate}if(!this.isConnected){throw"Not connected, cannot change playback rate."}this.signaling.send({type:"set_speed",play_rate:e})};this.getStats=function(e){this.peerConn.getStats().then(function(t){var n={};var i=Array.from(t.entries());for(var r in i){var s=i[r];if(s[1].type=="inbound-rtp"){n[s[0]]=s[1]}}e(n)})};this.connect()}function d(t){this.ws=null;this.ws=new WebSocket(e.source.url.replace(/^http/,"ws"));var n=false;this.ws.onopen=function(){t({type:"on_connected"})};this.ws.timeOut=e.timers.start(function(){if(e.player.webrtc.signaling.ws.readyState==0){e.log("WebRTC: socket timeout - try next combo");e.nextCombo()}},5e3);this.ws.onmessage=function(e){try{var n=JSON.parse(e.data);t(n)}catch(t){console.error("Failed to parse a response from MistServer",t,e.data)}};this.ws.onclose=function(e){switch(e.code){case 1006:{}default:{t({type:"on_disconnected",code:e.code});break}}};this.sendOfferSDP=function(e){this.send({type:"offer_sdp",offer_sdp:e})};this.send=function(e){if(!this.ws){throw"Not initialized, cannot send "+JSON.stringify(e)}this.ws.send(JSON.stringify(e))}}this.webrtc=new u;this.api={};var p;Object.defineProperty(this.api,"duration",{get:function(){return p}});Object.defineProperty(this.api,"currentTime",{get:function(){return c+r.currentTime},set:function(e){c=e-r.currentTime;r.pause();var t=n.webrtc.seek(e);MistUtil.event.send("seeking",e,r);if(t){t.catch(function(e){})}}});Object.defineProperty(this.api,"playbackRate",{get:function(){return n.webrtc.playbackrate()},set:function(e){return n.webrtc.playbackrate(e)}});function h(e){Object.defineProperty(n.api,e,{get:function(){return r[e]},set:function(t){return r[e]=t}})}var v=["volume","muted","loop","paused",,"error","textTracks","webkitDroppedFrameCount","webkitDecodedFrameCount"];for(var o in v){h(v[o])}function g(e){if(e in r){n.api[e]=function(){return r[e].call(r,arguments)}}}var v=["load","getVideoPlaybackQuality"];for(var o in v){g(v[o])}n.api.play=function(){var t;if(n.api.currentTime){t=n.api.currentTime}if(e.info&&e.info.type=="live"){t="live"}if(t){var i=new Promise(function(i,r){if(!n.webrtc.isConnected&&n.webrtc.peerConn.iceConnectionState!="completed"){if(!n.webrtc.isConnecting){e.log("Received call to play while not connected, connecting "+n.webrtc.peerConn.iceConnectionState);n.webrtc.connect(function(){n.webrtc.seek(t).then(function(e){i("played "+e)}).catch(function(e){r(e)})})}else{r("Still connecting")}}else{n.webrtc.seek(t).then(function(e){i("played "+e)}).catch(function(e){r(e)})}});return i}else{return r.play()}};n.api.getStats=function(){if(n.webrtc&&n.webrtc.isConnected){return new Promise(function(e,t){n.webrtc.peerConn.getStats().then(function(t){var n={audio:null,video:null};var i=Object.fromEntries(t);for(var r in i){if(i[r].type=="track"){n[i[r].kind]=i[r]}}e(n)})})}};n.api.getLatency=function(){var t=e.player.api.getStats();if(t){return new Promise(function(e,i){t.then(function(t){setTimeout(function(){var r=n.api.getStats();if(!r){i();return}r.then(function(n){var i={};for(var r in t){i[r]=t[r]&&n[r]?(n[r].jitterBufferDelay-t[r].jitterBufferDelay)/(n[r].jitterBufferEmittedCount-t[r].jitterBufferEmittedCount):null}e(i)},i)},1e3)},i)})}};n.api.pause=function(){r.pause();try{n.webrtc.pause()}catch(e){}MistUtil.event.send("paused",null,r)};n.api.setTracks=function(e){if(n.webrtc.isConnected){n.webrtc.setTrack(e)}else{var t=function(){n.webrtc.setTrack(e);MistUtil.event.removeListener({type:"webrtc_connected",callback:t,element:r})};MistUtil.event.addListener(r,"webrtc_connected",t)}};function b(){if(!n.api.textTracks[0]){return}var e=n.api.textTracks[0].currentOffset||0;if(Math.abs(c-e)<1){return}var t=[];for(var i=n.api.textTracks[0].cues.length-1;i>=0;i--){var r=n.api.textTracks[0].cues[i];n.api.textTracks[0].removeCue(r);if(!("orig"in r)){r.orig={start:r.startTime,end:r.endTime}}r.startTime=r.orig.start-c;r.endTime=r.orig.end-c;t.push(r)}for(var i in t){n.api.textTracks[0].addCue(t[i])}n.api.textTracks[0].currentOffset=c}n.api.setSubtitle=function(e){var t=r.getElementsByTagName("track");for(var n=t.length-1;n>=0;n--){r.removeChild(t[n])}if(e){var i=document.createElement("track");r.appendChild(i);i.kind="subtitles";i.label=e.label;i.srclang=e.lang;i.src=e.src;i.setAttribute("default","");i.onload=b}};n.api.metaTrackSocket=function(){this.origin={};this.CONNECTING=0;this.OPEN=1;this.CLOSING=2;this.CLOSED=3;this.readyState=0;this.listeners=[];var t=this;MistUtil.event.addListener(e.video,"webrtc_ready",function(){t.init()});this.init=function(){this.origin=e.player.webrtc&&e.player.webrtc.MetaDataTrack?e.player.webrtc.MetaDataTrack:{};if("readyState"in this.origin){function n(){t.readyState=t.OPEN;t.onopen()}this.origin.addEventListener("open",n);this.origin.onmessage=function(e){};this.origin.addEventListener("close",function(){t.readyState=t.CLOSED;t.onclose()});if(this.origin.readyState=="open"){n()}return true}else{return false}};this.open=function(){if(this.readyState==this.OPEN)return;switch(this.origin.readyState){case"connecting":{this.readyState=this.CONNECTING;break}case"open":{this.readyState=this.OPEN;break}case"closing":{this.readyState=this.CLOSING;break}case"closed":{this.readyState=this.CLOSED;break}}for(var e in this.listeners){this.origin.addEventListener.apply(this.origin,this.listeners[e])}};this.close=function(){if(this.readyState>=this.CLOSING)return;this.readyState=this.CLOSED;for(var e in this.listeners){this.removeEventListener.apply(this,this.listeners[e])}};this.send=function(){if(this.origin.readyState=="open")return this.origin.send.apply(this,arguments);return false};this.onopen=function(){};this.onclose=function(){};this.addEventListener=function(){this.listeners.push(arguments);return this.origin.addEventListener.apply(this.origin,arguments)};this.removeEventListener=function(e,t){for(var n=this.listeners.length-1;n>=0;n--){if(e==this.listeners[n][0]&&t==this.listeners[n][1]){this.listeners.splice(n,1);break}}return this.origin.removeEventListener.apply(this.origin,arguments)};this.init();return this};MistUtil.event.addListener(r,"ended",function(){if(n.api.loop){if(e.state=="Stream is online"){n.webrtc.connect()}}});if("decodingIssues"in e.skin.blueprints){var y=["nackCount","pliCount","packetsLost","packetsReceived","bytesReceived"];for(var w in y){n.api[y[w]]=0}var k=function(){e.timers.start(function(){n.webrtc.getStats(function(e){for(var t in e){for(var i in y){if(y[i]in e[t]){n.api[y[i]]=e[t][y[i]]}}break}});k()},1e3)};k()}n.api.ABR_resize=function(t){e.log("Requesting the video track with the resolution that best matches the player size");n.api.setTracks({video:"~"+[t.width,t.height].join("x")})};n.api.unload=function(){try{n.webrtc.stop();n.webrtc.signaling.ws.close();n.webrtc.peerConn.close()}catch(e){}};t(r)}; \ No newline at end of file +mistplayers.webrtc={name:"WebRTC player",mimes:["webrtc"],priority:MistUtil.object.keys(mistplayers).length+1,isMimeSupported:function(e){return this.mimes.indexOf(e)==-1?false:true},isBrowserSupported:function(e,t,n){if(!("WebSocket"in window)||(!("RTCPeerConnection"in window)||!("RTCRtpReceiver"in window))){return false}if(location.protocol.replace(/^http/,"ws")!=MistUtil.http.url.split(t.url.replace(/^http/,"ws")).protocol){n.log("HTTP/HTTPS mismatch for this source");return false}var i={};var r=false;for(var s in n.info.meta.tracks){if(n.info.meta.tracks[s].type=="meta"){if(n.info.meta.tracks[s].codec=="subtitle"){r=true}continue}if(!(n.info.meta.tracks[s].type in i)){i[n.info.meta.tracks[s].type]={}}i[n.info.meta.tracks[s].type][n.info.meta.tracks[s].codec]=1}var o=[];for(var a in i){var c=false;for(var l in i[a]){var f=RTCRtpReceiver.getCapabilities(a).codecs;for(var s in f){if(f[s].mimeType.toLowerCase()==(a+"/"+l).toLowerCase()){c=true;break}}}if(c){o.push(a)}}if(r){for(var s in n.info.source){if(n.info.source[s].type=="html5/text/vtt"){o.push("subtitle");break}}}return o.length?o:false},player:function(){}};var p=mistplayers.webrtc.player;p.prototype=new MistPlayer;p.prototype.build=function(e,t){var n=this;if(typeof WebRTCBrowserEqualizerLoaded=="undefined"||!WebRTCBrowserEqualizerLoaded){var i=document.createElement("script");i.src=e.urlappend(e.options.host+"/webrtc.js");e.log("Retrieving webRTC browser equalizer code from "+i.src);document.head.appendChild(i);i.onerror=function(){e.showError("Failed to load webrtc browser equalizer",{nextCombo:5})};i.onload=function(){n.build(e,t)};return}var r=document.createElement("video");r.setAttribute("playsinline","");var s=["autoplay","loop","poster"];for(var o in s){var a=s[o];if(e.options[a]){r.setAttribute(a,e.options[a]===true?"":e.options[a])}}if(e.options.muted){r.muted=true}if(e.info.type=="live"){r.loop=false}if(e.options.controls=="stock"){r.setAttribute("controls","")}r.setAttribute("crossorigin","anonymous");this.setSize=function(e){r.style.width=e.width+"px";r.style.height=e.height+"px"};MistUtil.event.addListener(r,"loadeddata",b);MistUtil.event.addListener(r,"seeked",b);if(!e.options.autoplay){MistUtil.event.addListener(r,"canplay",(function(){var t=MistUtil.event.addListener(r,"play",(function(){e.log("Pausing because autoplay is disabled");var i=MistUtil.event.addListener(r,"pause",(function(){e.options.autoplay=false;MistUtil.event.removeListener(i)}));n.api.pause();MistUtil.event.removeListener(t)}))}))}var c=0;var l=false;var f=[];this.listeners={on_connected:function(){c=0;l=false;this.webrtc.play();MistUtil.event.send("webrtc_connected",null,r)},on_disconnected:function(){MistUtil.event.send("webrtc_disconnected",null,r);e.log("Websocket sent on_disconnect");if(!l){r.pause()}},on_answer_sdp:function(t){if(!t.result){e.showError("Failed to open stream.");this.on_disconnected();return}e.log("SDP answer received")},on_time:function(t){var n=c;c=t.current*.001-r.currentTime;if(Math.abs(n-c)>1){b()}if((!("paused"in t)||!t.paused)&&r.paused){r.play()}var i=t.end==0?Infinity:t.end*.001;if(i!=p){p=i;MistUtil.event.send("durationchange",i,r)}e.info.meta.buffer_window=t.end-t.begin;if(t.tracks&&f!=t.tracks){var s=e.info?MistUtil.tracks.parse(e.info.meta.tracks):[];for(var o in t.tracks){if(f.indexOf(t.tracks[o])<0){var a;for(var l in s){if(t.tracks[o]in s[l]){a=l;break}}if(!a){continue}if(a=="subtitle"){continue}MistUtil.event.send("playerUpdate_trackChanged",{type:a,trackid:t.tracks[o]},e.video)}}f=t.tracks}if(e.reporting&&t.tracks){e.reporting.stats.d.tracks=t.tracks.join(",")}},seek:function(e){var t=this;MistUtil.event.send("seeked",c,r);if(e.live_point){t.webrtc.playbackrate("auto")}if("seekPromise"in this.webrtc.signaling){r.play().then((function(){if("seekPromise"in t.webrtc.signaling){t.webrtc.signaling.seekPromise.resolve("Play promise resolved")}})).catch((function(){if("seekPromise"in t.webrtc.signaling){t.webrtc.signaling.seekPromise.reject("Play promise rejected")}}))}else{r.play()}},set_speed:function(e){this.webrtc.play_rate=e.play_rate_curr;MistUtil.event.send("ratechange",e,r)},on_stop:function(){e.log("Websocket sent on_stop");r.pause();MistUtil.event.send("ended",null,r);l=true}};function u(){this.peerConn=null;this.localOffer=null;this.isConnected=false;this.isConnecting=false;this.play_rate="auto";var t=this;this.on_event=function(i){switch(i.type){case"on_connected":{t.isConnected=true;t.isConnecting=false;break}case"on_answer_sdp":{t.peerConn.setRemoteDescription({type:"answer",sdp:i.answer_sdp}).then((function(){}),(function(e){console.error(e)}));break}case"on_disconnected":{t.isConnected=false;break}case"on_error":{e.showError("WebRTC error: "+MistUtil.format.ucFirst(i.message));return;break}}if(i.type in n.listeners){return n.listeners[i.type].call(n,"data"in i?i.data:i)}e.log("Unhandled WebRTC event "+i.type+": "+JSON.stringify(i));return false};this.connect=function(n){t.isConnecting=true;e.container.setAttribute("data-loading","connecting");function i(e){var t=new Promise((function(t,n){function i(e){try{var r=RTCRtpReceiver.getCapabilities("video");for(var s=0;s0){setTimeout((function(){i(e-1)}),100)}else{n("H264 not found :(")}}catch(e){t("Checker unavailable")}}i(e)}));return t}i(5).catch((function(){e.log("Beware: this device does not seem to be able to play H264.")})).finally((function(){t.signaling=new d(t.on_event);var i={};if(e.options.RTCIceServers){i.iceServers=e.options.RTCIceServers}else if(e.source.RTCIceServers){i.iceServers=e.source.RTCIceServers}t.peerConn=new RTCPeerConnection(i);t.MetaDataTrack=t.peerConn.createDataChannel("*",{protocol:"JSON"});t.peerConn.ontrack=function(e){r.srcObject=e.streams[0];if(n){n()}};t.peerConn.ondatachannel=function(){console.warn("ondatachannel",arguments)};t.peerConn.onconnectionstatechange=function(t){if(e.destroyed){return}switch(this.connectionState){case"failed":{e.log("UDP connection failed, trying next combo.","error");e.nextCombo();break}case"connected":{e.container.removeAttribute("data-loading")}case"disconnected":case"closed":case"new":case"connecting":default:{e.log("WebRTC connection state changed to "+this.connectionState);break}}};t.peerConn.oniceconnectionstatechange=function(t){if(e.destroyed){return}switch(this.iceConnectionState){case"failed":{e.showError("ICE connection "+this.iceConnectionState);break}case"disconnected":case"closed":case"new":case"checking":case"connected":case"completed":default:{e.log("WebRTC ICE connection state changed to "+this.iceConnectionState);break}}};MistUtil.event.send("webrtc_ready",null,r)}))};this.play=function(){if(!this.isConnected){throw"Not connected, cannot play"}this.peerConn.createOffer({offerToReceiveAudio:true,offerToReceiveVideo:true}).then((function(e){t.localOffer=e;t.peerConn.setLocalDescription(e).then((function(){t.signaling.sendOfferSDP(t.localOffer.sdp)}),(function(e){console.error(e)}))}),(function(e){throw e}))};this.stop=function(){if(!this.isConnected){throw"Not connected, cannot stop."}this.signaling.send({type:"stop"})};this.seek=function(n){var i=new Promise((function(i,r){if(!t.isConnected||!t.signaling){if(t.isConnecting){var s=MistUtil.event.addListener(e.video,"loadstart",(function(){t.seek(n);MistUtil.event.removeListener(s)}));return r("Not connected yet, will seek once connected")}else{return r("Failed seek: not connected")}}t.signaling.send({type:"seek",seek_time:n=="live"?"live":n*1e3});if("seekPromise"in t.signaling){t.signaling.seekPromise.reject("Doing new seek")}t.signaling.seekPromise={resolve:function(e){i("seeked");delete t.signaling.seekPromise},reject:function(e){r("Failed to seek: "+e);delete t.signaling.seekPromise}}}));return i};this.pause=function(){if(!this.isConnected){throw"Not connected, cannot pause."}this.signaling.send({type:"hold"})};this.setTrack=function(e){if(!this.isConnected){throw"Not connected, cannot set track."}e.type="tracks";this.signaling.send(e)};this.playbackrate=function(e){if(typeof e=="undefined"){return n.webrtc.play_rate=="auto"?1:n.webrtc.play_rate}if(!this.isConnected){throw"Not connected, cannot change playback rate."}this.signaling.send({type:"set_speed",play_rate:e})};this.getStats=function(e){this.peerConn.getStats().then((function(t){var n={};var i=Array.from(t.entries());for(var r in i){var s=i[r];if(s[1].type=="inbound-rtp"){n[s[0]]=s[1]}}e(n)}))};this.connect()}function d(t){this.ws=null;this.ws=new WebSocket(e.source.url.replace(/^http/,"ws"));var n=false;this.ws.onopen=function(){t({type:"on_connected"})};this.ws.timeOut=e.timers.start((function(){if(e.player.webrtc.signaling.ws.readyState==0){e.log("WebRTC: socket timeout - try next combo");e.nextCombo()}}),5e3);this.ws.onmessage=function(e){try{var n=JSON.parse(e.data);t(n)}catch(t){console.error("Failed to parse a response from MistServer",t,e.data)}};this.ws.onclose=function(e){switch(e.code){case 1006:{}default:{t({type:"on_disconnected",code:e.code});break}}};this.sendOfferSDP=function(e){this.send({type:"offer_sdp",offer_sdp:e})};this.send=function(e){if(!this.ws){throw"Not initialized, cannot send "+JSON.stringify(e)}this.ws.send(JSON.stringify(e))}}this.webrtc=new u;this.api={};var p;Object.defineProperty(this.api,"duration",{get:function(){return p}});Object.defineProperty(this.api,"currentTime",{get:function(){return c+r.currentTime},set:function(e){c=e-r.currentTime;r.pause();var t=n.webrtc.seek(e);MistUtil.event.send("seeking",e,r);if(t){t.catch((function(e){}))}}});Object.defineProperty(this.api,"playbackRate",{get:function(){return n.webrtc.playbackrate()},set:function(e){return n.webrtc.playbackrate(e)}});function h(e){Object.defineProperty(n.api,e,{get:function(){return r[e]},set:function(t){return r[e]=t}})}var v=["volume","muted","loop","paused",,"error","textTracks","webkitDroppedFrameCount","webkitDecodedFrameCount"];for(var o in v){h(v[o])}function g(e){if(e in r){n.api[e]=function(){return r[e].call(r,arguments)}}}var v=["load","getVideoPlaybackQuality"];for(var o in v){g(v[o])}n.api.play=function(){var t;if(n.api.currentTime){t=n.api.currentTime}if(e.info&&e.info.type=="live"){t="live"}if(t){var i=new Promise((function(i,r){if(!n.webrtc.isConnected&&n.webrtc.peerConn.iceConnectionState!="completed"){if(!n.webrtc.isConnecting){e.log("Received call to play while not connected, connecting "+n.webrtc.peerConn.iceConnectionState);n.webrtc.connect((function(){n.webrtc.seek(t).then((function(e){i("played "+e)})).catch((function(e){r(e)}))}))}else{r("Still connecting")}}else{n.webrtc.seek(t).then((function(e){i("played "+e)})).catch((function(e){r(e)}))}}));return i}else{return r.play()}};n.api.getStats=function(){if(n.webrtc&&n.webrtc.isConnected){return new Promise((function(e,t){n.webrtc.peerConn.getStats().then((function(t){var n={audio:null,video:null};var i=Object.fromEntries(t);for(var r in i){if(i[r].type=="track"){n[i[r].kind]=i[r]}}e(n)}))}))}};n.api.getLatency=function(){var t=e.player.api.getStats();if(t){return new Promise((function(e,i){t.then((function(t){setTimeout((function(){var r=n.api.getStats();if(!r){i();return}r.then((function(n){var i={};for(var r in t){i[r]=t[r]&&n[r]?(n[r].jitterBufferDelay-t[r].jitterBufferDelay)/(n[r].jitterBufferEmittedCount-t[r].jitterBufferEmittedCount):null}e(i)}),i)}),1e3)}),i)}))}};n.api.pause=function(){r.pause();try{n.webrtc.pause()}catch(e){}MistUtil.event.send("paused",null,r)};n.api.setTracks=function(e){if(n.webrtc.isConnected){n.webrtc.setTrack(e)}else{var t=function(){n.webrtc.setTrack(e);MistUtil.event.removeListener({type:"webrtc_connected",callback:t,element:r})};MistUtil.event.addListener(r,"webrtc_connected",t)}};function b(){if(!n.api.textTracks[0]){return}var e=n.api.textTracks[0].currentOffset||0;if(Math.abs(c-e)<1){return}var t=[];for(var i=n.api.textTracks[0].cues.length-1;i>=0;i--){var r=n.api.textTracks[0].cues[i];n.api.textTracks[0].removeCue(r);if(!("orig"in r)){r.orig={start:r.startTime,end:r.endTime}}r.startTime=r.orig.start-c;r.endTime=r.orig.end-c;t.push(r)}for(var i in t){n.api.textTracks[0].addCue(t[i])}n.api.textTracks[0].currentOffset=c}n.api.setSubtitle=function(e){var t=r.getElementsByTagName("track");for(var n=t.length-1;n>=0;n--){r.removeChild(t[n])}if(e){var i=document.createElement("track");r.appendChild(i);i.kind="subtitles";i.label=e.label;i.srclang=e.lang;i.src=e.src;i.setAttribute("default","");i.onload=b}};n.api.metaTrackSocket=function(){this.origin={};this.CONNECTING=0;this.OPEN=1;this.CLOSING=2;this.CLOSED=3;this.readyState=0;this.listeners=[];var t=this;MistUtil.event.addListener(e.video,"webrtc_ready",(function(){t.init()}));this.init=function(){this.origin=e.player.webrtc&&e.player.webrtc.MetaDataTrack?e.player.webrtc.MetaDataTrack:{};if("readyState"in this.origin){function n(){t.readyState=t.OPEN;t.onopen()}this.origin.addEventListener("open",n);this.origin.onmessage=function(e){};this.origin.addEventListener("close",(function(){t.readyState=t.CLOSED;t.onclose()}));if(this.origin.readyState=="open"){n()}return true}else{return false}};this.open=function(){if(this.readyState==this.OPEN)return;switch(this.origin.readyState){case"connecting":{this.readyState=this.CONNECTING;break}case"open":{this.readyState=this.OPEN;break}case"closing":{this.readyState=this.CLOSING;break}case"closed":{this.readyState=this.CLOSED;break}}for(var e in this.listeners){this.origin.addEventListener.apply(this.origin,this.listeners[e])}};this.close=function(){if(this.readyState>=this.CLOSING)return;this.readyState=this.CLOSED;for(var e in this.listeners){this.removeEventListener.apply(this,this.listeners[e])}};this.send=function(){if(this.origin.readyState=="open")return this.origin.send.apply(this,arguments);return false};this.onopen=function(){};this.onclose=function(){};this.addEventListener=function(){this.listeners.push(arguments);return this.origin.addEventListener.apply(this.origin,arguments)};this.removeEventListener=function(e,t){for(var n=this.listeners.length-1;n>=0;n--){if(e==this.listeners[n][0]&&t==this.listeners[n][1]){this.listeners.splice(n,1);break}}return this.origin.removeEventListener.apply(this.origin,arguments)};this.init();return this};MistUtil.event.addListener(r,"ended",(function(){if(n.api.loop){if(e.state=="Stream is online"){n.webrtc.connect()}}}));if("decodingIssues"in e.skin.blueprints){var y=["nackCount","pliCount","packetsLost","packetsReceived","bytesReceived"];for(var w in y){n.api[y[w]]=0}var k=function(){e.timers.start((function(){n.webrtc.getStats((function(e){for(var t in e){for(var i in y){if(y[i]in e[t]){n.api[y[i]]=e[t][y[i]]}}break}}));k()}),1e3)};k()}n.api.ABR_resize=function(t){e.log("Requesting the video track with the resolution that best matches the player size");n.api.setTracks({video:"~"+[t.width,t.height].join("x")})};n.api.unload=function(){try{n.webrtc.stop();n.webrtc.signaling.ws.close();n.webrtc.peerConn.close()}catch(e){}};t(r)}; \ No newline at end of file diff --git a/lib/config.cpp b/lib/config.cpp index 74c4ccc4..20c0092b 100644 --- a/lib/config.cpp +++ b/lib/config.cpp @@ -763,6 +763,7 @@ void Util::Config::addStandardPushCapabilities(JSON::Value &cap){ pp["track_selectors"]["type"] = "group"; pp["track_selectors"]["name"] = "Track selectors"; pp["track_selectors"]["help"] = "Control which tracks are part of the output"; + pp["track_selectors"]["sort"] = "v"; { JSON::Value & o = pp["track_selectors"]["options"]; o["audio"]["name"] = "Audio track(s)"; @@ -787,6 +788,7 @@ void Util::Config::addStandardPushCapabilities(JSON::Value &cap){ pp["trackwait_opts"]["type"] = "group"; pp["trackwait_opts"]["name"] = "Wait for tracks"; pp["trackwait_opts"]["help"] = "Before starting, ensure the available tracks satisfy certain conditions"; + pp["trackwait_opts"]["sort"] = "w"; { JSON::Value & o = pp["trackwait_opts"]["options"]; o["waittrackcount"]["name"] = "Wait for tracks count"; @@ -823,37 +825,10 @@ void Util::Config::addStandardPushCapabilities(JSON::Value &cap){ o["maxwaittrackms"]["sort"] = "bdad"; } - pp["pls_opts"]["type"] = "group"; - pp["pls_opts"]["name"] = "Playlist writing options"; - pp["pls_opts"]["help"] = "Control the writing of a playlist file when recording to a segmented format"; - { - JSON::Value & o = pp["pls_opts"]["options"]; - o["noendlist"]["name"] = "Don't end playlist"; - o["noendlist"]["help"] = "If set, does not write #X-EXT-ENDLIST when finalizing the playlist on exit"; - o["noendlist"]["type"] = "bool"; - o["noendlist"]["format"] = "set_or_unset"; - o["noendlist"]["sort"] = "bfa"; - - o["m3u8"]["name"] = "Playlist path (relative to segments)"; - o["m3u8"]["help"] = "If set, will write a m3u8 playlist file for the segments to the given path (relative from the first segment path). When this parameter is used, at least one of the variables $segmentCounter or $currentMediaTime must be part of the segment path (to keep segments from overwriting each other). The \"Split interval\" parameter will default to 60 seconds when using this option."; - o["m3u8"]["type"] = "string"; - o["m3u8"]["sort"] = "apa"; - - o["targetAge"]["name"] = "Playlist target age"; - o["targetAge"]["help"] = "When writing a playlist, delete segment entries that are more than this many seconds old from the playlist (and, if possible, also delete said segments themselves). When set to 0 or left empty, does not delete."; - o["targetAge"]["type"] = "int"; - o["targetAge"]["unit"] = "s"; - o["targetAge"]["sort"] = "apb"; - - o["maxEntries"]["name"] = "Playlist max entries"; - o["maxEntries"]["help"] = "When writing a playlist, delete oldest segment entries once this entry count has been reached (and, if possible, also delete said segments themselves). When set to 0 or left empty, does not delete."; - o["maxEntries"]["type"] = "int"; - o["maxEntries"]["sort"] = "apc"; - } - pp["time_opts"]["type"] = "group"; pp["time_opts"]["name"] = "Timing options"; pp["time_opts"]["help"] = "Control speed and the start/stop timing"; + pp["time_opts"]["sort"] = "x"; { JSON::Value & o = pp["time_opts"]["options"]; o["rate"]["name"] = "Playback rate"; @@ -917,17 +892,55 @@ void Util::Config::addStandardPushCapabilities(JSON::Value &cap){ o["split"]["sort"] = "bh"; } - pp["unmask"]["name"] = "Unmask tracks"; - pp["unmask"]["help"] = "If set to any value, removes any applied track masking before selecting tracks, acting as if no mask was applied at all"; - pp["unmask"]["type"] = "bool"; - pp["unmask"]["format"] = "set_or_unset"; - pp["unmask"]["sort"] = "bc"; + pp["pls_opts"]["type"] = "group"; + pp["pls_opts"]["name"] = "Playlist writing options"; + pp["pls_opts"]["help"] = "Control the writing of a playlist file when recording to a segmented format"; + pp["pls_opts"]["sort"] = "y"; + { + JSON::Value & o = pp["pls_opts"]["options"]; + o["noendlist"]["name"] = "Don't end playlist"; + o["noendlist"]["help"] = "If set, does not write #X-EXT-ENDLIST when finalizing the playlist on exit"; + o["noendlist"]["type"] = "bool"; + o["noendlist"]["format"] = "set_or_unset"; + o["noendlist"]["sort"] = "bfa"; - pp["append"]["name"] = "Append to file"; - pp["append"]["help"] = "If set to any value, will (if possible) append to an existing file, rather than overwriting it"; - pp["append"]["type"] = "bool"; - pp["append"]["format"] = "set_or_unset"; - pp["append"]["sort"] = "bf"; + o["m3u8"]["name"] = "Playlist path (relative to segments)"; + o["m3u8"]["help"] = "If set, will write a m3u8 playlist file for the segments to the given path (relative from the first segment path). When this parameter is used, at least one of the variables $segmentCounter or $currentMediaTime must be part of the segment path (to keep segments from overwriting each other). The \"Split interval\" parameter will default to 60 seconds when using this option."; + o["m3u8"]["type"] = "string"; + o["m3u8"]["sort"] = "apa"; + + o["targetAge"]["name"] = "Playlist target age"; + o["targetAge"]["help"] = "When writing a playlist, delete segment entries that are more than this many seconds old from the playlist (and, if possible, also delete said segments themselves). When set to 0 or left empty, does not delete."; + o["targetAge"]["type"] = "int"; + o["targetAge"]["unit"] = "s"; + o["targetAge"]["sort"] = "apb"; + + o["maxEntries"]["name"] = "Playlist max entries"; + o["maxEntries"]["help"] = "When writing a playlist, delete oldest segment entries once this entry count has been reached (and, if possible, also delete said segments themselves). When set to 0 or left empty, does not delete."; + o["maxEntries"]["type"] = "int"; + o["maxEntries"]["sort"] = "apc"; + } + + + + pp["misc_genopts"]["type"] = "group"; + pp["misc_genopts"]["name"] = "Miscellaneous options"; + pp["misc_genopts"]["sort"] = "z"; + { + JSON::Value & o = pp["misc_genopts"]["options"]; + + o["unmask"]["name"] = "Unmask tracks"; + o["unmask"]["help"] = "If set to any value, removes any applied track masking before selecting tracks, acting as if no mask was applied at all"; + o["unmask"]["type"] = "bool"; + o["unmask"]["format"] = "set_or_unset"; + o["unmask"]["sort"] = "bc"; + + o["append"]["name"] = "Append to file"; + o["append"]["help"] = "If set to any value, will (if possible) append to an existing file, rather than overwriting it"; + o["append"]["type"] = "bool"; + o["append"]["format"] = "set_or_unset"; + o["append"]["sort"] = "bf"; + } } diff --git a/lsp/main.css b/lsp/main.css index 4534e4e8..83ffb6ba 100644 --- a/lsp/main.css +++ b/lsp/main.css @@ -637,6 +637,68 @@ input[type=radio] { margin-bottom: 1em; margin-left: 1em; } +.input_container .itemgroup .input_container:first-child > b { + cursor: pointer; + margin-right: 0.5em; +} +.input_container .itemgroup.expanded .input_container:first-child > b:before { + content: '▼'; +} +.input_container .itemgroup .input_container:first-child > b:before { + content: '▲'; + color: var(--accentColor); + opacity: 0.5; + transform: translateY(-0.1em); + display: inline-block; +} +.input_container .itemgroup .input_container:first-child:hover > b:before { + opacity: 1; +} +.input_container .itemgroup .description { + display: none; +} +.input_container .itemgroup.expanded .description { + display: block; +} + +.input_container .itemgroup .summary { + font-size: 0.8em; + margin: 0; + padding-inline: 1.75em; +} +.input_container .itemgroup .summary li::marker { + margin: 0 0.25em 0 0; +} +.input_container .itemgroup .summary:empty { + display: flex; + flex-flow: row wrap; + list-style: none; + padding: 0; +} +.input_container .itemgroup .summary:empty:after { + content: '(Default settings)'; +} +.input_container .itemgroup .summary:empty { + opacity: 0.5; +} +.input_container .itemgroup.expanded .summary { + display: none; +} +.input_container .itemgroup .UIelement { + display: none; +} +.input_container .itemgroup.expanded .UIelement { + display: flex; +} +.input_container .itemgroup:not(.expanded) .input_container:has(.summary:empty) { + flex-direction: row; + align-items: baseline; + margin: 0; +} +.input_container .itemgroup.expanded .input_container { + flex-direction: column; + margin: 0.5em 0; +} .input_container .subitem { font-size: 0.9em; color: #777; diff --git a/lsp/minified.js b/lsp/minified.js index d2b3d0ca..794ccd14 100644 --- a/lsp/minified.js +++ b/lsp/minified.js @@ -30,10 +30,10 @@ TS:-1,"TS.exe":-1},j;for(j in f){for(n in mist.data.config.protocols){var q=mist j.length&&(j[0].style.display="");g+=""==a?"STREAMNAME":a;break;case "TSSRT":case "TSSRT.exe":"srt://"==b.slice(0,6)?i?(g=parseURL(b.replace()),""==g.host&&(g=parseURL(b.replace(/^srt:\/\//,"http://localhost")),g.host=g.host.replace(/^localhost/,"")),g=""!=g.host&&(!g.search||!g.searchParams||"listener"!=g.searchParams.get("mode"))?"Caller mode: you should push to the other side.":g.search&&g.searchParams&&"caller"==g.searchParams.get("mode")?"Caller mode: you should probably add an address.":"srt://"+ c.host+i):g="You must specify a port.":g="srt://"+c.host+j+"?streamid="+(""==a?"STREAMNAME":a);break;case "RTSP":case "RTSP.exe":g="rtsp://"+c.host+j+"/"+(""==a?"STREAMNAME":a)+(e?"?pass="+e:"");break;case "TS":case "TS.exe":g="udp://"+(""==l?c.host:l)+j+"/"}j=d.find(".field."+n.replace(".exe",""));j.length&&(j.setval(g).closest("label")[0].style.display="")}}},buildUI:function(a){var b=$("
").addClass("input_container"),d;for(d in a){var c=a[d];if(c instanceof jQuery)b.append(c);else if("help"== c.type){var e=$("").addClass("text_container").append($("").addClass("description").append(c.help));b.append(e);if("classes"in c)for(var l in c.classes)e.addClass(c.classes[l])}else if("text"==c.type)b.append($("").addClass("text_container").append($("").addClass("text").append(c.text)));else if("custom"==c.type)b.append(c.custom);else if("buttons"==c.type)for(l in e=$("").addClass("button_container").on("keydown",function(a){a.stopPropagation()}),"css"in c&&e.css(c.css), -b.append(e),c.buttons){var i=c.buttons[l],f=$("