/* * SegmentTracker.cpp ***************************************************************************** * Copyright (C) 2014 - VideoLAN and VLC authors * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation; either version 2.1 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. *****************************************************************************/ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include "SegmentTracker.hpp" #include "SharedResources.hpp" #include "playlist/BasePlaylist.hpp" #include "playlist/BaseRepresentation.h" #include "playlist/BaseAdaptationSet.h" #include "playlist/Segment.h" #include "playlist/SegmentChunk.hpp" #include "logic/AbstractAdaptationLogic.h" #include "logic/BufferingLogic.hpp" #include #include using namespace adaptive; using namespace adaptive::logic; using namespace adaptive::playlist; TrackerEvent::TrackerEvent(Type t) { type = t; } TrackerEvent::~TrackerEvent() { } TrackerEvent::Type TrackerEvent::getType() const { return type; } DiscontinuityEvent::DiscontinuityEvent(uint64_t d) : TrackerEvent(Type::Discontinuity) { discontinuitySequenceNumber = d; } SegmentGapEvent::SegmentGapEvent() : TrackerEvent(Type::SegmentGap) { } RepresentationSwitchEvent::RepresentationSwitchEvent(BaseRepresentation *prev, BaseRepresentation *next) : TrackerEvent(Type::RepresentationSwitch) { this->prev = prev; this->next = next; } RepresentationUpdatedEvent::RepresentationUpdatedEvent(BaseRepresentation * rep) : TrackerEvent(Type::RepresentationUpdated) { this->rep = rep; } RepresentationUpdateFailedEvent::RepresentationUpdateFailedEvent(BaseRepresentation * rep) : TrackerEvent(Type::RepresentationUpdateFailed) { this->rep = rep; } FormatChangedEvent::FormatChangedEvent(const StreamFormat *f) : TrackerEvent(Type::FormatChange) { this->format = f; } SegmentChangedEvent::SegmentChangedEvent(const ID &id, uint64_t sequence, vlc_tick_t starttime, vlc_tick_t duration, vlc_tick_t displaytime) : TrackerEvent(Type::SegmentChange) { this->id = &id; this->sequence = sequence; this->duration = duration; this->starttime = starttime; this->displaytime = displaytime; } BufferingStateUpdatedEvent::BufferingStateUpdatedEvent(const ID &id, bool enabled) : TrackerEvent(Type::BufferingStateUpdate) { this->id = &id; this->enabled = enabled; } BufferingLevelChangedEvent::BufferingLevelChangedEvent(const ID &id, vlc_tick_t minimum, vlc_tick_t maximum, vlc_tick_t current, vlc_tick_t target) : TrackerEvent(Type::BufferingLevelChange) { this->id = &id; this->minimum = minimum; this->maximum = maximum; this->current = current; this->target = target; } PositionChangedEvent::PositionChangedEvent(vlc_tick_t r) : TrackerEvent(Type::PositionChange) { resumeTime = r; } SegmentTracker::SegmentTracker(SharedResources *res, AbstractAdaptationLogic *logic_, const AbstractBufferingLogic *bl, BaseAdaptationSet *adaptSet, SynchronizationReferences *refs) { resources = res; first = true; initializing = true; bufferingLogic = bl; setAdaptationLogic(logic_); adaptationSet = adaptSet; synchronizationReferences = refs; format = StreamFormat::Type::Unknown; } SegmentTracker::~SegmentTracker() { reset(); } SegmentTracker::Position::Position() { number = std::numeric_limits::max(); rep = nullptr; init_sent = false; index_sent = false; } SegmentTracker::Position::Position(BaseRepresentation *rep, uint64_t number) { this->rep = rep; this->number = number; init_sent = false; index_sent = false; } bool SegmentTracker::Position::isValid() const { return number != std::numeric_limits::max() && rep != nullptr; } std::string SegmentTracker::Position::toString() const { std::stringstream ss; ss.imbue(std::locale("C")); if(isValid()) ss << "seg# " << number << " " << init_sent << ":" << index_sent << " " << rep->getID().str(); else ss << "invalid"; return ss.str(); } SegmentTracker::Position & SegmentTracker::Position::operator ++() { if(isValid()) { if(index_sent) ++number; else if(init_sent) index_sent = true; else init_sent = true; return *this; } return *this; } void SegmentTracker::setAdaptationLogic(AbstractAdaptationLogic *logic_) { logic = logic_; registerListener(logic); } void SegmentTracker::getCodecsDesc(CodecDescriptionList *descs) const { BaseRepresentation *rep = current.rep; if(!rep) rep = logic->getNextRepresentation(adaptationSet, nullptr); if(rep) rep->getCodecsDesc(descs); } const Role & SegmentTracker::getStreamRole() const { return adaptationSet->getRole(); } void SegmentTracker::reset() { notify(RepresentationSwitchEvent(current.rep, nullptr)); current = Position(); next = Position(); resetChunksSequence(); initializing = true; format = StreamFormat::Type::Unknown; } SegmentTracker::ChunkEntry::ChunkEntry() { chunk = nullptr; } SegmentTracker::ChunkEntry::ChunkEntry(SegmentChunk *c, Position p, vlc_tick_t s, vlc_tick_t d, vlc_tick_t dt) { chunk = c; pos = p; duration = d; starttime = s; displaytime = dt; } bool SegmentTracker::ChunkEntry::isValid() const { return chunk && pos.isValid(); } SegmentTracker::ChunkEntry SegmentTracker::prepareChunk(bool switch_allowed, Position pos) const { if(!adaptationSet) return ChunkEntry(); /* starting */ if(!pos.isValid()) { pos = getStartPosition(); if(!pos.isValid()) return ChunkEntry(); } else /* continuing, or seek */ { if(!adaptationSet->isSegmentAligned() || !pos.init_sent || !pos.index_sent) switch_allowed = false; if(switch_allowed) { Position temp; temp.rep = logic->getNextRepresentation(adaptationSet, pos.rep); if(temp.rep && temp.rep != pos.rep) { /* Convert our segment number if we need to */ temp.number = temp.rep->translateSegmentNumber(pos.number, pos.rep); /* Ensure ephemere content is updated/loaded */ if(temp.rep->needsUpdate(temp.number)) temp.rep->scheduleNextUpdate(temp.number, temp.rep->runLocalUpdates(resources)); /* could have been std::numeric_limits::max() if not found because not avail */ if(!temp.isValid()) /* try again */ temp.number = temp.rep->translateSegmentNumber(pos.number, pos.rep); /* cancel switch that would go past playlist */ if(temp.isValid() && temp.rep->getMinAheadTime(temp.number) == 0) temp = Position(); } if(temp.isValid()) pos = temp; } } bool b_gap = true; ISegment *datasegment = pos.rep->getNextMediaSegment(pos.number, &pos.number, &b_gap); if(!datasegment && (!pos.rep->needsIndex() || pos.index_sent)) return ChunkEntry(); ISegment *segment = nullptr; if(!pos.init_sent) { segment = pos.rep->getInitSegment(); if(!segment) ++pos; } if(!segment && !pos.index_sent) { if(pos.rep->needsIndex()) segment = pos.rep->getIndexSegment(); if(!segment) ++pos; } if(!segment) segment = datasegment; SegmentChunk *segmentChunk = segment->toChunk(resources, pos.number, pos.rep); if(!segmentChunk) return ChunkEntry(); if(segment != datasegment && datasegment) /* need to set for init */ segmentChunk->discontinuitySequenceNumber = datasegment->getDiscontinuitySequenceNumber(); vlc_tick_t startTime = VLC_TICK_INVALID; vlc_tick_t duration = 0; vlc_tick_t displayTime = datasegment ? datasegment->getDisplayTime() : VLC_TICK_INVALID; /* timings belong to timeline and are not set on the segment or need profile timescale */ if(pos.rep->getPlaybackTimeDurationBySegmentNumber(pos.number, &startTime, &duration)) startTime += VLC_TICK_0; return ChunkEntry(segmentChunk, pos, startTime, duration, displayTime); } void SegmentTracker::resetChunksSequence() { while(!chunkssequence.empty()) { delete chunkssequence.front().chunk; chunkssequence.pop_front(); } } ChunkInterface * SegmentTracker::getNextChunk(bool switch_allowed) { if(!adaptationSet || !next.isValid()) return nullptr; if(chunkssequence.empty()) { ChunkEntry chunk = prepareChunk(switch_allowed, next); chunkssequence.push_back(chunk); } ChunkEntry chunk = chunkssequence.front(); if(!chunk.isValid()) { chunkssequence.pop_front(); delete chunk.chunk; return nullptr; } /* here next == wanted chunk pos */ bool b_gap = (next.number != chunk.pos.number); const bool b_switched = (current.rep != chunk.pos.rep) || !current.rep; bool b_discontinuity = chunk.chunk->discontinuity && current.isValid(); if(b_discontinuity && current.number == next.number) { /* if we are on the same segment and indexes have been sent, then discontinuity was */ b_discontinuity = false; } const uint64_t discontinuitySequenceNumber = chunk.chunk->discontinuitySequenceNumber; if(b_switched) { notify(RepresentationSwitchEvent(current.rep, chunk.pos.rep)); initializing = true; } /* advance or don't trigger duplicate events */ next = current = chunk.pos; if(format == StreamFormat(StreamFormat::Type::Unsupported)) return nullptr; /* Can't return chunk because no demux will be created */ /* From this point chunk must be returned */ ChunkInterface *returnedChunk; StreamFormat chunkformat = chunk.chunk->getStreamFormat(); /* Wrap and probe format */ if(chunkformat == StreamFormat(StreamFormat::Type::Unknown)) { ProbeableChunk *wrappedck = new ProbeableChunk(chunk.chunk); const uint8_t *p_peek; size_t i_peek = wrappedck->peek(&p_peek); chunkformat = StreamFormat(p_peek, i_peek); /* fallback on Mime type */ if(chunkformat == StreamFormat(StreamFormat::Type::Unknown)) chunkformat = StreamFormat(chunk.chunk->getContentType()); chunk.chunk->setStreamFormat(chunkformat); returnedChunk = wrappedck; } else returnedChunk = chunk.chunk; if(chunkformat != format && chunkformat != StreamFormat(StreamFormat::Type::Unknown)) { format = chunkformat; notify(FormatChangedEvent(&format)); } /* pop position and return our chunk */ chunkssequence.pop_front(); chunk.chunk = nullptr; if(initializing) { b_gap = false; /* stop initializing after 1st chunk */ initializing = false; } if(b_gap) notify(SegmentGapEvent()); /* Handle both implicit and explicit discontinuities */ if(b_discontinuity) notify(DiscontinuityEvent(discontinuitySequenceNumber)); /* Notify new segment length for stats / logic */ notify(SegmentChangedEvent(adaptationSet->getID(), discontinuitySequenceNumber, chunk.starttime, chunk.duration, chunk.displaytime)); if(!b_gap) ++next; return returnedChunk; } bool SegmentTracker::setPositionByTime(vlc_tick_t time, bool restarted, bool tryonly) { Position pos = Position(current.rep, current.number); if(!pos.isValid()) pos.rep = logic->getNextRepresentation(adaptationSet, nullptr); if(!pos.rep) return false; /* Stream might not have been loaded at all (HLS) or expired */ if(pos.rep->needsUpdate(pos.number)) { if(!pos.rep->runLocalUpdates(resources)) { msg_Err(adaptationSet->getPlaylist()->getVLCObject(), "Failed to update Representation %s", pos.rep->getID().str().c_str()); return false; } pos.rep->scheduleNextUpdate(pos.number, true); notify(RepresentationUpdatedEvent(pos.rep)); } if(pos.rep->getSegmentNumberByTime(time, &pos.number)) { if(!tryonly) setPosition(pos, restarted); return true; } return false; } void SegmentTracker::setPosition(const Position &pos, bool restarted) { if(restarted) initializing = true; current = Position(); next = pos; resetChunksSequence(); notify(PositionChangedEvent(getPlaybackTime(true))); } SegmentTracker::Position SegmentTracker::getStartPosition() const { Position pos; pos.rep = logic->getNextRepresentation(adaptationSet, nullptr); if(pos.rep) { /* Ensure ephemere content is updated/loaded */ bool b_updated = pos.rep->needsUpdate(pos.number) && pos.rep->runLocalUpdates(resources); pos.number = bufferingLogic->getStartSegmentNumber(pos.rep); pos.rep->scheduleNextUpdate(pos.number, b_updated); if(b_updated) notify(RepresentationUpdatedEvent(pos.rep)); } return pos; } bool SegmentTracker::setStartPosition() { if(next.isValid()) return true; Position pos = getStartPosition(); if(!pos.isValid()) return false; next = pos; return true; } vlc_tick_t SegmentTracker::getPlaybackTime(bool b_next) const { vlc_tick_t time, duration; BaseRepresentation *rep = current.rep; if(!rep) rep = logic->getNextRepresentation(adaptationSet, nullptr); if(rep && rep->getPlaybackTimeDurationBySegmentNumber(b_next ? next.number : current.number, &time, &duration)) { return time; } return 0; } bool SegmentTracker::getMediaPlaybackRange(vlc_tick_t *start, vlc_tick_t *end, vlc_tick_t *length) const { if(!current.rep) return false; return current.rep->getMediaPlaybackRange(start, end, length); } vlc_tick_t SegmentTracker::getMinAheadTime() const { BaseRepresentation *rep = current.rep; if(!rep) rep = logic->getNextRepresentation(adaptationSet, nullptr); if(rep) { /* Ensure ephemere content is updated/loaded */ if(rep->needsUpdate(next.number)) { bool b_updated = rep->runLocalUpdates(resources); rep->scheduleNextUpdate(next.number, b_updated); if(b_updated) notify(RepresentationUpdatedEvent(rep)); } uint64_t startnumber = current.number; if(startnumber == std::numeric_limits::max()) startnumber = bufferingLogic->getStartSegmentNumber(rep); if(startnumber != std::numeric_limits::max()) return rep->getMinAheadTime(startnumber); } return 0; } bool SegmentTracker::getSynchronizationReference(uint64_t discontinuitysequence, vlc_tick_t time, SynchronizationReference &r) const { return synchronizationReferences->getReference(discontinuitysequence, time, r); } void SegmentTracker::updateSynchronizationReference(uint64_t discontinuitysequence, const Times &t) { synchronizationReferences->addReference(discontinuitysequence, t); } void SegmentTracker::notifyBufferingState(bool enabled) const { notify(BufferingStateUpdatedEvent(adaptationSet->getID(), enabled)); } void SegmentTracker::notifyBufferingLevel(vlc_tick_t min, vlc_tick_t max, vlc_tick_t current, vlc_tick_t target) const { notify(BufferingLevelChangedEvent(adaptationSet->getID(), min, max, current, target)); } void SegmentTracker::registerListener(SegmentTrackerListenerInterface *listener) { listeners.push_back(listener); } bool SegmentTracker::bufferingAvailable() const { if(adaptationSet->getPlaylist()->isLive()) return getMinAheadTime() > 0; return true; } bool SegmentTracker::updateSelected() { bool b_updated; if(current.rep && current.rep->needsUpdate(next.number)) { b_updated = current.rep->runLocalUpdates(resources); current.rep->scheduleNextUpdate(current.number, b_updated); if(b_updated) notify(RepresentationUpdatedEvent(current.rep)); } else b_updated = false; if(current.rep && current.rep->canNoLongerUpdate()) notify(RepresentationUpdateFailedEvent(current.rep)); return b_updated; } void SegmentTracker::notify(const TrackerEvent &event) const { std::list::const_iterator it; for(it=listeners.begin();it != listeners.end(); ++it) (*it)->trackerEvent(event); }