Working PlayReady/Verimatrix DRM support

This commit is contained in:
Erik Zandvliet 2015-05-07 19:38:43 +02:00 committed by Thulinma
parent 27fdbb2468
commit 0913d2607e
25 changed files with 1360 additions and 103 deletions

View file

@ -53,6 +53,22 @@ namespace Mist {
option["long"] = "stream";
option["help"] = "The name of the stream that this connector will provide in player mode";
config->addOption("streamname", option);
/*LTS-START*/
//Encryption
option["arg"] = "string";
option["long"] = "verimatrix-playready";
option["short"] = "P";
option["help"] = "URL of the Verimatrix PlayReady keyserver";
config->addOption("verimatrix-playready", option);
capa["optional"]["verimatrix-playready"]["name"] = "Verimatrix PlayReady Server";
capa["optional"]["verimatrix-playready"]["help"] = "URL of the Verimatrix PlayReady keyserver";
capa["optional"]["verimatrix-playready"]["option"] = "--verimatrix-playready";
capa["optional"]["verimatrix-playready"]["type"] = "str";
capa["optional"]["verimatrix-playready"]["default"] = "";
option.null();
/*LTS-END*/
capa["optional"]["debug"]["name"] = "debug";
capa["optional"]["debug"]["help"] = "The debug level at which messages need to be printed.";

View file

@ -45,6 +45,7 @@ namespace Mist {
capa["optional"]["record"]["type"] = "str";
capa["optional"]["record"]["default"] = "";
option.null();
option["arg"] = "integer";
option["long"] = "cut";
option["short"] = "c";
@ -57,6 +58,7 @@ namespace Mist {
capa["optional"]["cut"]["type"] = "uint";
capa["optional"]["cut"]["default"] = 0LL;
option.null();
option["arg"] = "integer";
option["long"] = "segment-size";
option["short"] = "S";
@ -68,6 +70,7 @@ namespace Mist {
capa["optional"]["segmentsize"]["option"] = "--segment-size";
capa["optional"]["segmentsize"]["type"] = "uint";
capa["optional"]["segmentsize"]["default"] = 5000LL;
option.null();
/*LTS-end*/
capa["source_match"] = "push://*";
capa["priority"] = 9ll;
@ -746,6 +749,7 @@ namespace Mist {
recBpos = 0;
}
*/
/*LTS-END*/
configLock.post();
configLock.close();

View file

@ -0,0 +1,158 @@
#include <iostream>
#include <cstring>
#include <cerrno>
#include <cstdlib>
#include <cstdio>
#include <string>
#include <mist/stream.h>
#include <mist/base64.h>
#include <mist/defines.h>
#include <mist/encryption.h>
#include <mist/bitfields.h>
#include "input_dtsccrypt.h"
#include <ctime>
namespace Mist {
inputDTSC::inputDTSC(Util::Config * cfg) : Input(cfg) {
capa["name"] = "DTSC";
capa["desc"] = "Enables DTSC Input";
capa["priority"] = 9ll;
capa["source_match"] = "/*.dtsc";
capa["codecs"][0u][0u].append("H264");
capa["codecs"][0u][0u].append("H263");
capa["codecs"][0u][0u].append("VP6");
capa["codecs"][0u][0u].append("theora");
capa["codecs"][0u][1u].append("AAC");
capa["codecs"][0u][1u].append("MP3");
capa["codecs"][0u][1u].append("vorbis");
JSON::Value option;
option["long"] = "key";
option["short"] = "k";
option["arg"] = "string";
option["help"] = "The key to en/decrypt the current file with";
config->addOption("key", option);
option.null();
option["long"] = "keyseed";
option["short"] = "s";
option["arg"] = "string";
option["help"] = "The keyseed to en/decrypt the current file with";
config->addOption("keyseed", option);
option.null();
option["long"] = "keyid";
option["short"] = "i";
option["arg"] = "string";
option["help"] = "The keyid to en/decrypt the current file with";
config->addOption("keyid", option);
option.null();
srand(time(NULL));
}
bool inputDTSC::setup() {
key = Base64::decode(config->getString("key"));
if (key == ""){
if (config->getString("keyseed") == "" || config->getString("keyid") == ""){
std::cerr << "No key given, and no keyseed/keyid geven" << std::endl;
return false;
}
std::string tmpSeed = Base64::decode(config->getString("keyseed"));
std::string tmpID = Base64::decode(config->getString("keyid"));
std::string guid = Encryption::PR_GuidToByteArray(tmpID);
key = Encryption::PR_GenerateContentKey(tmpSeed, guid);
}
if (config->getString("input") == "-") {
std::cerr << "Input from stdin not yet supported" << std::endl;
return false;
}
if (!config->getString("streamname").size()){
if (config->getString("output") == "-") {
std::cerr << "Output to stdout not yet supported" << std::endl;
return false;
}
}else{
if (config->getString("output") != "-") {
std::cerr << "File output in player mode not supported" << std::endl;
return false;
}
}
//open File
inFile = DTSC::File(config->getString("input"));
if (!inFile) {
return false;
}
return true;
}
bool inputDTSC::readHeader() {
if (!inFile) {
return false;
}
DTSC::File tmp(config->getString("input") + ".dtsh");
if (tmp) {
myMeta = tmp.getMeta();
DEBUG_MSG(DLVL_HIGH,"Meta read in with %lu tracks", myMeta.tracks.size());
return true;
}
if (inFile.getMeta().moreheader < 0 || inFile.getMeta().tracks.size() == 0) {
DEBUG_MSG(DLVL_FAIL,"Missing external header file");
return false;
}
myMeta = DTSC::Meta(inFile.getMeta());
DEBUG_MSG(DLVL_DEVEL,"Meta read in with %lu tracks", myMeta.tracks.size());
return true;
}
void inputDTSC::getNext(bool smart) {
if (smart){
inFile.seekNext();
}else{
inFile.parseNext();
}
thisPacket = inFile.getPacket();
//Do encryption/decryption here
int tid = thisPacket.getTrackId();
char * ivec;
unsigned int ivecLen;
thisPacket.getString("ivec", ivec, ivecLen);
char iVec[16];
if (ivecLen){
memcpy(iVec, ivec, 8);
}else{
if (iVecs.find(tid) == iVecs.end()){
iVecs[tid] = ((long long unsigned int)rand() << 32) + rand();
}
Bit::htobll(iVec, iVecs[tid]);
iVecs[tid] ++;
}
Encryption::encryptPlayReady(thisPacket, myMeta.tracks[tid].codec, iVec, key.data());
}
void inputDTSC::seek(int seekTime) {
inFile.seek_time(seekTime);
initialTime = 0;
playUntil = 0;
}
void inputDTSC::trackSelect(std::string trackSpec) {
selectedTracks.clear();
long long unsigned int index;
while (trackSpec != "") {
index = trackSpec.find(' ');
selectedTracks.insert(atoi(trackSpec.substr(0, index).c_str()));
if (index != std::string::npos) {
trackSpec.erase(0, index + 1);
} else {
trackSpec = "";
}
}
inFile.selectTracks(selectedTracks);
}
}

View file

@ -0,0 +1,25 @@
#include "input.h"
#include <mist/dtsc.h>
namespace Mist {
class inputDTSC : public Input {
public:
inputDTSC(Util::Config * cfg);
protected:
//Private Functions
bool setup();
bool readHeader();
void getNext(bool smart = true);
void seek(int seekTime);
void trackSelect(std::string trackSpec);
DTSC::File inFile;
std::map<int,unsigned long long int> iVecs;
std::string key;
};
}
typedef Mist::inputDTSC mistIn;

View file

@ -1,3 +1,9 @@
#include <mist/stream.h>
#include <mist/json.h>
#include <mist/auth.h>
#include <mist/base64.h>
#include <mist/bitfields.h>
#include <cstdlib>
#include "io.h"
namespace Mist {
@ -16,8 +22,42 @@ namespace Mist {
//Write the metadata to the page
myMeta.writeTo(metaPages[0].mapped);
}
/*LTS-START*/
void InOutBase::initiateEncryption(){
static bool encInit = false;
if (encInit){
return;
}
encrypt = false;
JSON::Value cfg = Util::getStreamConfig(streamName);
vmData.url = cfg.isMember("verimatrix-playready")?cfg["verimatrix-playready"].asString():"";
vmData.name = streamName;
if (vmData.url != ""){
Encryption::fillVerimatrix(vmData);
}else{
vmData.keyid = cfg.isMember("keyid")?cfg["keyid"].asString():"";
vmData.keyseed = cfg.isMember("keyseed")?cfg["keyseed"].asString():"";
if (vmData.keyid != "" && vmData.keyseed != ""){
vmData.keyid = Base64::decode(vmData.keyid);
vmData.keyseed = Base64::decode(vmData.keyseed);
vmData.key = Encryption:: PR_GenerateContentKey(vmData.keyseed, vmData.keyid);
vmData.laurl = cfg.isMember("la_url")?cfg["la_url"].asString():"";
}
}
if (vmData.key != ""){
encrypt = true;
char pageName[NAME_BUFFER_SIZE];
snprintf(pageName, NAME_BUFFER_SIZE, SHM_STREAM_ENCRYPT, streamName.c_str());
encryptionPage.init(pageName, 8 * 1024 * 1024, true);
vmData.write(encryptionPage.mapped);
}
encInit = true;
}
/*LTS-END*/
///Starts the buffering of a new page.
///
///Does not do any actual buffering, just sets the right bits for buffering to go right.
@ -27,6 +67,7 @@ namespace Mist {
///\param pageNumber The number of the page to start buffering
bool InOutBase::bufferStart(unsigned long tid, unsigned long pageNumber) {
VERYHIGH_MSG("bufferStart for stream %s, track %lu, page %lu", streamName.c_str(), tid, pageNumber);
initiateEncryption();
//Initialize the stream metadata if it does not yet exist
if (!metaPages.count(0)) {
initiateMeta();
@ -79,12 +120,19 @@ namespace Mist {
//Open the correct page for the data
char pageId[NAME_BUFFER_SIZE];
snprintf(pageId, NAME_BUFFER_SIZE, SHM_TRACK_DATA, streamName.c_str(), mapTid, pageNumber);
std::string pageName(pageId);
int pageSize = pagesByTrack[tid][pageNumber].dataSize;
#ifdef __CYGWIN__
curPage[tid].init(pageName, 26 * 1024 * 1024, true);
#else
curPage[tid].init(pageName, pagesByTrack[tid][pageNumber].dataSize, true);
pageSize = 26 * 1024 * 1024;
#endif
/*LTS-START*/
INFO_MSG("Page size %d", pageSize);
if (encrypt){
pageSize = pageSize * 1.5;
INFO_MSG("Adjusted page size to %d", pageSize);
}
/*LTS-END*/
std::string pageName(pageId);
curPage[tid].init(pageName, pageSize, true);
//Make sure the data page is not destroyed when we are done buffering it later on.
curPage[tid].master = false;
//Store the pagenumber of the currently buffer page
@ -223,27 +271,52 @@ namespace Mist {
}
//Brain melt starts here
char iVec[16];
if (encrypt){
if (iVecs.find(tid) == iVecs.end()){
iVecs[tid] = ((long long unsigned int)rand() << 32) + rand();
}
Bit::htobll(iVec, iVecs[tid]);
iVecs[tid] ++;
Encryption::encryptPlayReady(pack, myMeta.tracks[tid].codec, iVec, vmData.key.data());
}
//First memcpy only the payload to the destination
//Leaves the 20 bytes inbetween empty to ensure the data is not accidentally read before it is complete
memcpy(curPage[tid].mapped + curOffset + 20, pack.getData() + 20, pack.getDataLen() - 20);
if (encrypt){
//write ivec field + new object end at (currOffset + pack.getDataLen() - 3);
int ivecOffset = curOffset + pack.getDataLen() - 3;
memcpy(curPage[tid].mapped+ivecOffset, "\000\004ivec\002\000\000\000\010", 11);
memcpy(curPage[tid].mapped+ivecOffset+11, iVec, 8);
//finish container with 0x0000EE
memcpy(curPage[tid].mapped+ivecOffset+19, "\000\000\356", 3);
}
//Copy the remaing values in reverse order:
//8 byte timestamp
memcpy(curPage[tid].mapped + curOffset + 12, pack.getData() + 12, 8);
//The mapped track id
((int *)(curPage[tid].mapped + curOffset + 8))[0] = htonl(mapTid);
//Write the size and 'DTP2' bytes to conclude the packet and allow for reading it
memcpy(curPage[tid].mapped + curOffset, pack.getData(), 8);
int size = Bit::btohl(pack.getData() + 4);
if (encrypt){
//Alter size to reflect the addition of the ivec field ( + 19 )
size += 19;
}
//Write the size
Bit::htobl(curPage[tid].mapped + curOffset + 4, size);
//write the 'DTP2' bytes to conclude the packet and allow for reading it
memcpy(curPage[tid].mapped + curOffset, pack.getData(), 4);
if (myMeta.live){
//Update the metadata
DTSC::Packet updatePack(curPage[tid].mapped + curOffset, pack.getDataLen(), true);
DTSC::Packet updatePack(curPage[tid].mapped + curOffset, size + 8, true);
myMeta.update(updatePack);
}
//End of brain melt
pagesByTrack[tid][curPageNum[tid]].curOffset += pack.getDataLen();
pagesByTrack[tid][curPageNum[tid]].curOffset += size + 8;
}
///Wraps up the buffering of a shared memory data page
@ -497,8 +570,8 @@ namespace Mist {
metaPages[tid].init(pageName, 8 * 1024 * 1024, true);
metaPages[tid].master = false;
DTSC::Meta tmpMeta;
tmpMeta.tracks[tid] = myMeta.tracks[tid];
tmpMeta.tracks[tid].trackID = newTid;
tmpMeta.tracks[newTid] = myMeta.tracks[tid];
tmpMeta.tracks[newTid].trackID = newTid;
JSON::Value tmpVal = tmpMeta.toJSON();
std::string tmpStr = tmpVal.toNetPacked();
memcpy(metaPages[tid].mapped, tmpStr.data(), tmpStr.size());

View file

@ -6,6 +6,7 @@
#include <mist/defines.h>
#include <mist/dtsc.h>
#include <mist/encryption.h>//LTS
namespace Mist {
enum negotiationState {
FILL_NEW,///< New track, just sent negotiation request
@ -40,6 +41,7 @@ namespace Mist {
protected:
bool standAlone;
static Util::Config * config;
void initiateEncryption();//LTS
void continueNegotiate(unsigned long tid);
@ -64,5 +66,10 @@ namespace Mist {
std::map<unsigned long, unsigned long> curPageNum;///< For each track, holds the number page that is currently being written.
std::map<unsigned long, IPC::sharedPage> curPage;///< For each track, holds the page that is currently being written.
std::map<unsigned long, std::deque<DTSC::Packet> > trackBuffer; ///< Buffer to be used during active track negotiation
bool encrypt;
Encryption::verimatrixData vmData;
std::map<int,unsigned long long int> iVecs;
IPC::sharedPage encryptionPage;
};
}

View file

@ -7,8 +7,10 @@
#include <mist/base64.h>
#include <mist/http_parser.h>
#include <mist/stream.h>
#include <mist/bitfields.h>
#include <mist/checksum.h>
#include <unistd.h>
#include <mist/nal.h>/*LTS*/
///\todo Maybe move to util?
long long unsigned int binToInt(std::string & binary) {
@ -58,11 +60,9 @@ namespace Mist {
capa["methods"][0u]["handler"] = "http";
capa["methods"][0u]["type"] = "html5/application/vnd.ms-ss";
capa["methods"][0u]["priority"] = 9ll;
capa["methods"][0u]["nolive"] = 1;
capa["methods"][1u]["handler"] = "http";
capa["methods"][1u]["type"] = "silverlight";
capa["methods"][1u]["priority"] = 1ll;
capa["methods"][1u]["nolive"] = 1;
}
void OutHSS::sendNext() {
@ -203,11 +203,11 @@ namespace Mist {
//Wrap everything in mp4 boxes
MP4::MFHD mfhd_box;
mfhd_box.setSequenceNumber(((keyObj.getNumber() - 1) * 2) + tid);///\todo Urgent: Check this for multitrack... :P wtf... :P
mfhd_box.setSequenceNumber(((keyObj.getNumber() - 1) * 2) + (myMeta.tracks[tid].type == "video" ? 1 : 2));
MP4::TFHD tfhd_box;
tfhd_box.setFlags(MP4::tfhdSampleFlag);
tfhd_box.setTrackID(tid);
tfhd_box.setTrackID((myMeta.tracks[tid].type == "video" ? 1 : 2));
if (myMeta.tracks[tid].type == "video") {
tfhd_box.setDefaultSampleFlags(0x00004001);
} else {
@ -256,6 +256,11 @@ namespace Mist {
//If the stream is live, we want to have a fragref box if possible
//////HEREHEREHERE
if (myMeta.live) {
MP4::UUID_TFXD tfxd_box;
tfxd_box.setTime(keyObj.getTime());
tfxd_box.setDuration(keyObj.getLength());
traf_box.setContent(tfxd_box, 3);
MP4::UUID_TrackFragmentReference fragref_box;
fragref_box.setVersion(1);
fragref_box.setFragmentCount(0);
@ -268,42 +273,47 @@ namespace Mist {
fragref_box.setFragmentCount(++fragCount);
}
}
traf_box.setContent(fragref_box, 3);
traf_box.setContent(fragref_box, 4);
}
MP4::MOOF moof_box;
moof_box.setContent(mfhd_box, 0);
moof_box.setContent(traf_box, 1);
/*LTS-START*/
if (myMeta.tracks[tid].keys.size() == myMeta.tracks[tid].ivecs.size()) {
std::string tmpVec = std::string(myMeta.tracks[tid].ivecs[keyObj.getNumber() - myMeta.tracks[tid].keys[0].getNumber()].getData(), 8);
unsigned long long int curVec = binToInt(tmpVec);
if (encrypt){
MP4::UUID_SampleEncryption sEnc;
sEnc.setVersion(0);
if (myMeta.tracks[tid].type == "audio") {
sEnc.setFlags(0);
for (int i = 0; i < keyObj.getParts(); i++) {
MP4::UUID_SampleEncryption_Sample newSample;
newSample.InitializationVector = intToBin(curVec);
curVec++;
prepareNext();
thisPacket.getString("ivec", newSample.InitializationVector);
sEnc.setSample(newSample, i);
}
} else {
sEnc.setFlags(2);
std::deque<long long int> tmpParts;
for (int i = 0; i < keyObj.getParts(); i++) {
//Get the correct packet
prepareNext();
MP4::UUID_SampleEncryption_Sample newSample;
newSample.InitializationVector = intToBin(curVec);
curVec++;
MP4::UUID_SampleEncryption_Sample_Entry newEntry;
newEntry.BytesClear = 5;
newEntry.BytesEncrypted = myMeta.tracks[tid].parts[partOffset + i].getSize() - 5;
newSample.Entries.push_back(newEntry);
thisPacket.getString("ivec", newSample.InitializationVector);
std::deque<int> nalSizes = h264::parseNalSizes(thisPacket);
for(std::deque<int>::iterator it = nalSizes.begin(); it != nalSizes.end(); it++){
int encrypted = (*it - 5) & ~0xF;//Bitmask to a multiple of 16
MP4::UUID_SampleEncryption_Sample_Entry newEntry;
newEntry.BytesClear = *it - encrypted;//Size + nal_unit_type
newEntry.BytesEncrypted = encrypted;//Entire NAL except nal_unit_type;
newSample.Entries.push_back(newEntry);
}
sEnc.setSample(newSample, i);
}
}
traf_box.setContent(sEnc, 3);
}
seek(seekTime);
/*LTS-END*/
//Setting the correct offsets.
moof_box.setContent(traf_box, 1);
@ -324,11 +334,27 @@ namespace Mist {
}
/*LTS-START*/
std::string OutHSS::protectionHeader(JSON::Value & encParams) {
void OutHSS::loadEncryption(){
static bool encryptionLoaded = false;
if (!encryptionLoaded){
//Load the encryption data page
char pageName[NAME_BUFFER_SIZE];
snprintf(pageName, NAME_BUFFER_SIZE, SHM_STREAM_ENCRYPT, streamName.c_str());
encryptionPage.init(pageName, 8 * 1024 * 1024, false, false);
if (encryptionPage.mapped) {
vmData.read(encryptionPage.mapped);
encrypt = true;
}
encryptionLoaded = true;
}
}
std::string OutHSS::protectionHeader() {
loadEncryption();
std::string xmlGen = "<WRMHEADER xmlns=\"http://schemas.microsoft.com/DRM/2007/03/PlayReadyHeader\" version=\"4.0.0.0\"><DATA><PROTECTINFO><KEYLEN>16</KEYLEN><ALGID>AESCTR</ALGID></PROTECTINFO><KID>";
xmlGen += encParams["keyid"].asString();
xmlGen += vmData.keyid;
xmlGen += "</KID><LA_URL>";
xmlGen += encParams["la_url"].asString();
xmlGen += vmData.laurl;
xmlGen += "</LA_URL></DATA></WRMHEADER>";
std::string tmp = toUTF16(xmlGen);
tmp = tmp.substr(2);
@ -346,13 +372,12 @@ namespace Mist {
}
/*LTS-END*/
///\brief Builds an index file for HTTP Smooth streaming.
///\param encParams The encryption parameters. /*LTS*/
///\return The index file for HTTP Smooth Streaming.
/*LTS
std::string smoothIndex(){
LTS*/
std::string OutHSS::smoothIndex(JSON::Value encParams) { /*LTS*/
std::string OutHSS::smoothIndex(){
loadEncryption();//LTS
updateMeta();
std::stringstream Result;
Result << "<?xml version=\"1.0\" encoding=\"utf-16\"?>\n";
@ -366,7 +391,6 @@ namespace Mist {
long long int maxHeight = 0;
long long int minWidth = 99999999;
long long int minHeight = 99999999;
bool encrypted = false;/*LTS*/
for (std::map<unsigned int, DTSC::Track>::iterator it = myMeta.tracks.begin(); it != myMeta.tracks.end(); it++) {
if (it->second.codec == "AAC") {
audioIters.push_back(it);
@ -410,7 +434,6 @@ namespace Mist {
"Url=\"Q({bitrate},{CustomAttributes})/A({start time})\">\n";
int index = 0;
for (std::deque<std::map<unsigned int, DTSC::Track>::iterator>::iterator it = audioIters.begin(); it != audioIters.end(); it++) {
encrypted |= ((*it)->second.keys.size() == (*it)->second.ivecs.size()); /*LTS*/
Result << "<QualityLevel "
"Index=\"" << index << "\" "
"Bitrate=\"" << (*it)->second.bps * 8 << "\" "
@ -456,7 +479,6 @@ namespace Mist {
"DisplayHeight=\"" << maxHeight << "\">\n";
int index = 0;
for (std::deque<std::map<unsigned int, DTSC::Track>::iterator>::iterator it = videoIters.begin(); it != videoIters.end(); it++) {
encrypted |= ((*it)->second.keys.size() == (*it)->second.ivecs.size()); /*LTS*/
//Add video qualities
Result << "<QualityLevel "
"Index=\"" << index << "\" "
@ -490,9 +512,9 @@ namespace Mist {
Result << "</StreamIndex>\n";
}
/*LTS-START*/
if (encrypted) {
if (encrypt) {
Result << "<Protection><ProtectionHeader SystemID=\"9a04f079-9840-4286-ab92-e65be0885f95\">";
Result << protectionHeader(encParams);
Result << protectionHeader();
Result << "</ProtectionHeader></Protection>";
}
/*LTS-END*/
@ -517,16 +539,14 @@ namespace Mist {
return;
}
initialize();
loadEncryption();//LTS
if (H.url.find("Manifest") != std::string::npos) {
//Manifest, direct reply
H.Clean();
H.SetHeader("Content-Type", "text/xml");
H.SetHeader("Cache-Control", "no-cache");
H.setCORSHeaders();
/*LTS
std::string manifest = smoothIndex();
LTS*/
std::string manifest = smoothIndex(encryption);/*LTS*/
H.SetBody(manifest);
H.SendResponse("200", "OK", myConn);
H.Clean();
@ -536,17 +556,4 @@ namespace Mist {
sendHeader();
}
}
/*LTS-START*/
void OutHSS::initialize() {
Output::initialize();
JSON::Value servConf = JSON::fromFile(Util::getTmpFolder() + "streamlist");
encryption["keyseed"] = servConf["streams"][streamName]["keyseed"];
encryption["keyid"] = servConf["streams"][streamName]["keyid"];
encryption["contentkey"] = servConf["streams"][streamName]["contentkey"];
encryption["la_url"] = servConf["streams"][streamName]["la_url"];
servConf.null();
}
/*LTS-END*/
}

View file

@ -9,15 +9,11 @@ namespace Mist {
static void init(Util::Config * cfg);
void onHTTP();
void sendNext();
void initialize();/*LTS*/
void sendHeader();
protected:
JSON::Value encryption;
std::string protectionHeader(JSON::Value & encParams);/*LTS*/
/*LTS
std::string protectionHeader();/*LTS*/
std::string smoothIndex();
LTS*/
std::string smoothIndex(JSON::Value encParams = JSON::Value());/*LTS*/
void loadEncryption();/*LTS*/
int canSeekms(unsigned int ms);
int keysToSend;
int myTrackStor;