Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature conformance warning #3522

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -429,6 +429,7 @@ declare namespace dashjs {
CAN_PLAY: 'canPlay';
CAPTION_RENDERED: 'captionRendered';
CAPTION_CONTAINER_RESIZE: 'captionContainerResize';
CONFORMANCE_VIOLATION: 'conformanceViolation'
DYNAMIC_TO_STATIC: 'dynamicToStatic';
ERROR: 'error';
FRAGMENT_LOADING_ABANDONED: 'fragmentLoadingAbandoned';
Expand Down
22 changes: 22 additions & 0 deletions samples/dash-if-reference-player/app/css/main.css
Original file line number Diff line number Diff line change
Expand Up @@ -568,3 +568,25 @@ CONTROL BAR CSS NEEDS TO MOVE INTO THE CONTROL BAR
font-size: .9em !important;
padding: 0px;
}

/********************************************************
Conformance warnings
*********************************************************/
.label-Warning {
background-color: #f0ad4e;
}

.label-Error {
background-color: #d9534f;
}

.label-Suggestion {
background-color: #5bc0de;
}

.conformance-violations-panel {
background-color: #ffffff;
margin-top: 15px;
margin-bottom: 15px;
padding: 12px;
}
16 changes: 16 additions & 0 deletions samples/dash-if-reference-player/app/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,8 @@ app.controller('DashController', ['$scope', '$window', 'sources', 'contributors'

$scope.isDynamic = false;

$scope.conformanceViolations = [];

// metrics
$scope.videoBitrate = 0;
$scope.videoIndex = 0;
Expand Down Expand Up @@ -414,6 +416,18 @@ app.controller('DashController', ['$scope', '$window', 'sources', 'contributors'
}
}, $scope);

$scope.player.on(dashjs.MediaPlayer.events.CONFORMANCE_VIOLATION, function (e) { /* jshint ignore:line */
if (e && e.event && e.event.key && !$scope.conformanceViolations[e.event.key]) {
var existingViolation = $scope.conformanceViolations.filter(function (violation) {
return violation.event.key === e.event.key;
})

if(!existingViolation || existingViolation.length === 0) {
$scope.conformanceViolations.push(e);
}
}
}, $scope);

////////////////////////////////////////
//
// General Player Methods
Expand Down Expand Up @@ -685,6 +699,7 @@ app.controller('DashController', ['$scope', '$window', 'sources', 'contributors'
$scope.player.updateSettings(config);

$scope.controlbar.reset();
$scope.conformanceViolations = [];
if ($scope.isCasting) {
loadCastMedia($scope.selectedItem.url, protData);
} else {
Expand Down Expand Up @@ -721,6 +736,7 @@ app.controller('DashController', ['$scope', '$window', 'sources', 'contributors'
$scope.doStop = function () {
$scope.player.attachSource(null);
$scope.controlbar.reset();
$scope.conformanceViolations = [];
stopMetricsInterval();
};

Expand Down
113 changes: 68 additions & 45 deletions samples/dash-if-reference-player/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -174,13 +174,15 @@
<label class="topcoat-checkbox" data-toggle="tooltip" data-placement="right"
title="Enables calculation of the DVR window based on the timestamps in SegmentTimeline element.">
<input type="checkbox" ng-model="calcSegmentAvailabilityRangeFromTimelineSelected"
ng-change="toggleCalcSegmentAvailabilityRangeFromTimeline()" ng-checked="calcSegmentAvailabilityRangeFromTimelineSelected">
ng-change="toggleCalcSegmentAvailabilityRangeFromTimeline()"
ng-checked="calcSegmentAvailabilityRangeFromTimelineSelected">
Calculate segment availability from timeline
</label>
<label class="topcoat-checkbox" data-toggle="tooltip" data-placement="right"
title="Enable reuse of existing MediaSource Sourcebuffers during period transition.">
<input type="checkbox" ng-model="reuseExistingSourceBuffersSelected"
ng-change="toggleReuseExistingSourceBuffers()" ng-checked="reuseExistingSourceBuffersSelected">
ng-change="toggleReuseExistingSourceBuffers()"
ng-checked="reuseExistingSourceBuffersSelected">
Reuse SourceBuffers
</label>
<label class="topcoat-checkbox" data-toggle="tooltip" data-placement="right"
Expand Down Expand Up @@ -458,53 +460,60 @@

</div>

<!--VIDEO PLAYER / CONTROLS -->
<div class="row">
<div class="dash-video-player col-md-9">
<div id="videoContainer" class="videoContainer">
<video></video>
<div id="video-caption"></div>
<div id="cast-msg" ng-if="isCasting">
{{ castPlayerState === 'IDLE' ? 'Ready to cast stream' : castPlayerState }}
<!--VIDEO PLAYER / CONTROLS -->
<div class="row">
<div class="dash-video-player col-md-9">
<div id="videoContainer" class="videoContainer">
<video></video>
<div id="video-caption"></div>
<div id="cast-msg" ng-if="isCasting">
{{ castPlayerState === 'IDLE' ? 'Ready to cast stream' : castPlayerState }}
</div>
<div id="videoController" class="video-controller unselectable" ng-cloak>
<div id="playPauseBtn" class="btn-play-pause" data-toggle="tooltip" data-placement="bottom"
title="Play/Pause">
<span id="iconPlayPause" class="icon-play"></span>
</div>
<div id="videoController" class="video-controller unselectable" ng-cloak>
<div id="playPauseBtn" class="btn-play-pause" data-toggle="tooltip" data-placement="bottom" title="Play/Pause">
<span id="iconPlayPause" class="icon-play"></span>
</div>
<span id="videoTime" class="time-display">00:00:00</span>
<div id="fullscreenBtn" class="btn-fullscreen control-icon-layout" data-toggle="tooltip" data-placement="bottom" title="Fullscreen">
<span class="icon-fullscreen-enter"></span>
</div>
<div id="castBtn" class="btn-cast control-icon-layout" data-toggle="tooltip" data-placement="bottom" title="Cast">
<google-cast-launcher> </google-cast-launcher>
</div>
<div id="bitrateListBtn" class="btn-bitrate control-icon-layout" data-toggle="tooltip" data-placement="bottom" title="Bitrate List">
<span class="icon-bitrate"></span>
</div>
<input type="range" id="volumebar" class="volumebar" value="1" min="0" max="1" step=".01"/>
<div id="muteBtn" class="btn-mute control-icon-layout" data-toggle="tooltip" data-placement="bottom" title="Mute">
<span id="iconMute" class="icon-mute-off"></span>
</div>
<div id="trackSwitchBtn" class="btn-track-switch control-icon-layout" data-toggle="tooltip" data-placement="bottom" title="Track List">
<span class="icon-tracks"></span>
</div>
<div id="captionBtn" class="btn-caption control-icon-layout" data-toggle="tooltip" data-placement="bottom" title="Closed Caption / Subtitles">
<span class="icon-caption"></span>
</div>
<span id="videoDuration" class="duration-display">00:00:00</span>
<div class="seekContainer">
<div id="seekbar" class="seekbar seekbar-complete">
<div id="seekbar-buffer" class="seekbar seekbar-buffer"></div>
<div id="seekbar-play" class="seekbar seekbar-play"></div>
</div>
</div>
<div id="thumbnail-container" class="thumbnail-container">
<div id="thumbnail-elem" class="thumbnail-elem"></div>
<div id="thumbnail-time-label" class="thumbnail-time-label"></div>
<span id="videoTime" class="time-display">00:00:00</span>
<div id="fullscreenBtn" class="btn-fullscreen control-icon-layout" data-toggle="tooltip"
data-placement="bottom" title="Fullscreen">
<span class="icon-fullscreen-enter"></span>
</div>
<div id="castBtn" class="btn-cast control-icon-layout" data-toggle="tooltip" data-placement="bottom"
title="Cast">
<google-cast-launcher></google-cast-launcher>
</div>
<div id="bitrateListBtn" class="btn-bitrate control-icon-layout" data-toggle="tooltip"
data-placement="bottom" title="Bitrate List">
<span class="icon-bitrate"></span>
</div>
<input type="range" id="volumebar" class="volumebar" value="1" min="0" max="1" step=".01"/>
<div id="muteBtn" class="btn-mute control-icon-layout" data-toggle="tooltip" data-placement="bottom"
title="Mute">
<span id="iconMute" class="icon-mute-off"></span>
</div>
<div id="trackSwitchBtn" class="btn-track-switch control-icon-layout" data-toggle="tooltip"
data-placement="bottom" title="Track List">
<span class="icon-tracks"></span>
</div>
<div id="captionBtn" class="btn-caption control-icon-layout" data-toggle="tooltip"
data-placement="bottom" title="Closed Caption / Subtitles">
<span class="icon-caption"></span>
</div>
<span id="videoDuration" class="duration-display">00:00:00</span>
<div class="seekContainer">
<div id="seekbar" class="seekbar seekbar-complete">
<div id="seekbar-buffer" class="seekbar seekbar-buffer"></div>
<div id="seekbar-play" class="seekbar seekbar-play"></div>
</div>
</div>
<div id="thumbnail-container" class="thumbnail-container">
<div id="thumbnail-elem" class="thumbnail-elem"></div>
<div id="thumbnail-time-label" class="thumbnail-time-label"></div>
</div>
</div>
</div>
</div>

<!-- STATS TAB CONTENT -->
<div class="col-md-3 tabs-section">
Expand Down Expand Up @@ -689,8 +698,9 @@
</div>
</div>
</div>

</div>

<!-- ERROR MODAL -->
<div class="modal fade" id="errorModal" tabindex="-1" role="dialog" aria-labelledby="errorModalLabel"
aria-hidden="true">
Expand Down Expand Up @@ -729,6 +739,19 @@ <h5 class="modal-title" id="errorModalLabel">Error {{errorType}}</h5>
</div>
</div>

<!-- Conformance violations -->
<div class="row">
<div class="col-md-12 conformance-violations-panel">
<div id="conformance-violations">
<h4>Conformance Violations </h4>
<ul class="list-unstyled" ng-repeat="conformanceViolation in conformanceViolations">
<li><span class="label label-{{conformanceViolation.level}}">{{conformanceViolation.level}}</span> : {{conformanceViolation.event.message}} </li>
</ul>
</div>
</div>
</div>


</div>

<!-- FOOTER -->
Expand Down
6 changes: 6 additions & 0 deletions src/streaming/MediaPlayerEvents.js
Original file line number Diff line number Diff line change
Expand Up @@ -372,6 +372,12 @@ class MediaPlayerEvents extends EventsBase {
* @event MediaPlayerEvents#EVENT_MODE_ON_RECEIVE
*/
this.EVENT_MODE_ON_RECEIVE = 'eventModeOnReceive';

/**
* Event that is dispatched whenever the player encounters a potential conformance validation that might lead to unexpected/not optimal behavior
* @event MediaPlayerEvents#CONFORMANCE_VIOLATION
*/
this.CONFORMANCE_VIOLATION = 'conformanceViolation';
}
}

Expand Down
17 changes: 17 additions & 0 deletions src/streaming/constants/ConformanceViolationConstants.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
export default {
LEVELS: {
SUGGESTION: 'Suggestion',
WARNING: 'Warning',
ERROR: 'Error'
},
EVENTS: {
NO_UTC_TIMING_ELEMENT: {
key: 'NO_UTC_TIMING_ELEMENT',
message: 'No UTCTiming element is present in the manifest. You may experience playback failures. For a detailed validation use https://conformance.dashif.org/'
},
NON_COMPLIANT_SMPTE_IMAGE_ATTRIBUTE: {
key: 'NON_COMPLIANT_SMPTE_IMAGE_ATTRIBUTE',
message: 'SMPTE 2052-1:2013 defines the attribute name as "imageType" and does not define "imagetype"'
}
}
};
9 changes: 9 additions & 0 deletions src/streaming/controllers/StreamController.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ import MediaSourceController from './MediaSourceController';
import DashJSError from '../vo/DashJSError';
import Errors from '../../core/errors/Errors';
import EventController from './EventController';
import ConformanceViolationConstants from '../constants/ConformanceViolationConstants';

const PLAYBACK_ENDED_TIMER_INTERVAL = 200;
const PREBUFFERING_CAN_START_INTERVAL = 500;
Expand Down Expand Up @@ -850,6 +851,14 @@ function StreamController() {
adapter.updatePeriods(manifest);

let manifestUTCTimingSources = adapter.getUTCTimingSources();

if (adapter.getIsDynamic() && (!manifestUTCTimingSources || manifestUTCTimingSources.length === 0)) {
eventBus.trigger(MediaPlayerEvents.CONFORMANCE_VIOLATION, {
level: ConformanceViolationConstants.LEVELS.WARNING,
event: ConformanceViolationConstants.EVENTS.NO_UTC_TIMING_ELEMENT
});
}

let allUTCTimingSources = (!adapter.getIsDynamic()) ? manifestUTCTimingSources : manifestUTCTimingSources.concat(mediaPlayerModel.getUTCTimingSources());
const isHTTPS = urlUtils.isHTTPS(e.manifest.url);

Expand Down
13 changes: 10 additions & 3 deletions src/streaming/utils/TTMLParser.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,9 @@ import FactoryMaker from '../../core/FactoryMaker';
import Debug from '../../core/Debug';
import EventBus from '../../core/EventBus';
import Events from '../../core/events/Events';
import { fromXML, generateISD } from 'imsc';
import {fromXML, generateISD} from 'imsc';
import MediaPlayerEvents from '../MediaPlayerEvents';
import ConformanceViolationConstants from '../constants/ConformanceViolationConstants';

function TTMLParser() {

Expand Down Expand Up @@ -83,12 +85,16 @@ function TTMLParser() {
onOpenTag: function (ns, name, attrs) {
// cope with existing non-compliant content
if (attrs[' imagetype'] && !attrs[' imageType']) {
eventBus.trigger(MediaPlayerEvents.CONFORMANCE_VIOLATION, {
level: ConformanceViolationConstants.LEVELS.ERROR,
event: ConformanceViolationConstants.EVENTS.NON_COMPLIANT_SMPTE_IMAGE_ATTRIBUTE
});
attrs[' imageType'] = attrs[' imagetype'];
}

if (name === 'image' &&
(ns === 'http://www.smpte-ra.org/schemas/2052-1/2010/smpte-tt' ||
ns === 'http://www.smpte-ra.org/schemas/2052-1/2013/smpte-tt')) {
(ns === 'http://www.smpte-ra.org/schemas/2052-1/2010/smpte-tt' ||
ns === 'http://www.smpte-ra.org/schemas/2052-1/2013/smpte-tt')) {
if (!attrs[' imageType'] || attrs[' imageType'].value !== 'PNG') {
logger.warn('smpte-tt imageType != PNG. Discarded');
return;
Expand Down Expand Up @@ -168,5 +174,6 @@ function TTMLParser() {
setup();
return instance;
}

TTMLParser.__dashjs_factory_name = 'TTMLParser';
export default FactoryMaker.getSingletonFactory(TTMLParser);