Open Bug 1441829 Opened 8 years ago Updated 9 months ago

Audio duration almost always 268435.453125 (74:33:55) when src is data base64 encoded, but it plays normally

Categories

(Core :: Audio/Video: Playback, defect, P3)

58 Branch
defect

Tracking

()

UNCONFIRMED

People

(Reporter: frane, Unassigned)

References

Details

User Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.167 Safari/537.36 Steps to reproduce: I'm using html5 audio tag with src="data:audio/x-wav;base64,{base64data}". I js code I do following: - I save audio control reference in the variable var audioCtrl = document.getElementById(audioId); Actual results: - on "canplaythrough" event I try to get duration audioCtrl.duration (I tried with and without delay, and also tried some other events like durationchange, loadeddata, loadedmetadata, canplay, but result is the same), but I randomly get correct duration, but in approx. 80% of the times I get 268435.453125 (which is 74 hours, 33 minutes and 55 seconds), and my audio files' durations are between 5 seconds and 2 minutes. I even tried to use audioCtrl.seekable.end(audioCtrl.seekable.length - 1) and audioCtrl.buffered.end(audioCtrl.buffered.length - 1), but the result was identical. Expected results: Naturally, I expected to get an actual duration of my audio files.
I also tried to use native browser's audio controls, and they show the wrong duration too: http://take.ms/bmFYu Chrome and Safari show correct duration, and files play normally, so I don't think the data is corrupted.
Component: Audio/Video → Audio/Video: Playback
Thanks for the report! We definitely have some issues with calculating the correct duration for data urls. Can you please attach a testcase so we can trace the exact code path you're seeing? As a work-around, you can play in-document data with the webaudio api.
Priority: -- → P3
See Also: → 1436678
I only have base64 decoded audio data I get from the api. I don't see how webaudio api is going to help me get proper duration. The playback works normally otherwise.
I use angular 1.5. This is a custom directive (this is not the original code, some changes have been made in order to try to fix the issue and debug, but it should still work): .directive('customAudioControls', ['$timeout', '$interval', '$rootScope', function($timeout, $interval, $rootScope) { var lang = $rootScope.languageString; return { restrict: 'AE', replace: true, scope: { audioId: '=', haveFile: '=', fileLoading: '=', autoplay: '@', }, link: function($scope) { $scope.isPlaying = false; $scope.isFirefox = navigator.userAgent.toLowerCase().indexOf('firefox') > -1; $scope.pauseStr = ' ' + lang('audio_pause'); $scope.playStr = ' ' + lang('audio_play'); $scope.currentTime = 0; $scope.duration = 0; var audioCtrl; var playbackEnded = function(e) { $timeout(function() { $scope.isPlaying = false; $scope.currentTime = audioCtrl.currentTime = 0; }); }; var updateDuration = function(e) { $timeout(function() { $scope.duration = Number(audioCtrl.duration); }, $scope.isFirefox ? 100 : 10); }; $scope.userSeeked = function(e) { audioCtrl.currentTime = Number($scope.currentTime || 0); }; var updateTime = function(e) { $timeout(function() { $scope.currentTime = audioCtrl.currentTime; }); }; $scope.playPause = function() { if ($scope.haveFile && audioCtrl) { audioCtrl[$scope.isPlaying ? 'pause' : 'play'](); $scope.isPlaying = !$scope.isPlaying; } }; var hasBeenAutoplayed = false; var autoplayIfNeeded = function() { //console.log('canplaythrough'); if ($scope.autoplay !== undefined && String($scope.autoplay) !== 'false' && !hasBeenAutoplayed) { hasBeenAutoplayed = true; $timeout(function() { audioCtrl.play(); $scope.isPlaying = true; }, 550); } }; var updateDurationInterval; var cancelUpdateDurationTimeout; var cancelUpdateDurationInterval = function() { updateDurationInterval && $interval.cancel(updateDurationInterval); }; var startLoadDurationLoop = function() { $timeout(function() { cancelUpdateDurationInterval(); updateDurationInterval = $interval(updateDuration, 500); cancelUpdateDurationTimeout && $timeout.cancel(cancelUpdateDurationTimeout); cancelUpdateDurationTimeout = $timeout(cancelUpdateDurationInterval, 2000); }, $scope.isFirefox ? 150 : 60); }; var clearEvents = function() { if (audioCtrl) { angular.element(audioCtrl).off('timeupdate', updateTime); angular.element(audioCtrl).off('canplaythrough', startLoadDurationLoop); angular.element(audioCtrl).off('canplaythrough', autoplayIfNeeded); angular.element(audioCtrl).off('canplaythrough', updateDuration); // durationchange loadeddata loadedmetadata angular.element(audioCtrl).off('ended', playbackEnded); angular.element(audioCtrl).off('stop', playbackEnded); audioCtrl = null; cancelUpdateDurationInterval(); cancelUpdateDurationTimeout && $timeout.cancel(cancelUpdateDurationTimeout); } }; var unregister = $scope.$watch('haveFile', function(newValue) { if (newValue && $scope.audioId) { $timeout(function() { audioCtrl = document.getElementById($scope.audioId); if (audioCtrl) { hasBeenAutoplayed = false; audioCtrl.volume = 1.0; angular.element(audioCtrl).on('stop', playbackEnded); angular.element(audioCtrl).on('ended', playbackEnded); angular.element(audioCtrl).on('canplaythrough', updateDuration); // durationchange loadeddata loadedmetadata angular.element(audioCtrl).on('canplaythrough', autoplayIfNeeded); angular.element(audioCtrl).on('canplaythrough', startLoadDurationLoop); angular.element(audioCtrl).on('timeupdate', updateTime); $scope.currentTime = 0; } }); } else { clearEvents(); $timeout(function() { $scope.isPlaying = false; }); } }); $scope.$on('$destroy', unregister); $scope.$on('$destroy', clearEvents); }, template: '<div ng-show="haveFile||fileLoading" id="{{audioId}}-controls" class="btn btn-sm btn-primary in-group custom-audio-controls" style="display: inline-block; vertical-align: middle; height: 34px; line-height: inherit; width: auto; cursor: default; min-width: 160px !important;">\ <div ng-if="!haveFile&&fileLoading" style="height: 24px; padding: 0px; position: relative;">\ <div class="sk-spinner sk-spinner-wave" ng-if="!haveFile&&fileLoading" style="width: 55px; position: absolute; top: 0; right: 0; bottom: 0; left: 0; margin: auto;"><div class="sk-rect1" style="background-color: #fff;"></div><div class="sk-rect2" style="background-color: #fff;"></div><div class="sk-rect3" style="background-color: #fff;"></div><div class="sk-rect4" style="background-color: #fff;"></div><div class="sk-rect5" style="background-color: #fff;"></div></div>\ </div>\ <div ng-show="haveFile">\ <div ng-click="playPause()" style="float: left; margin: 2px; width: 48px; text-align: center; cursor: pointer;">\ <i class="fa" ng-class="isPlaying?{\'fa-pause\': true}:{\'fa-play\': true}"></i>\ <span>{{isPlaying?pauseStr:playStr}}</span>\ </div>\ <input style="float: left; width: auto !important; display: inline-block; margin-left: 3px; margin-right: 3px; vertical-align: middle; cursor: pointer;" ng-style="::{\'margin-top\': isFirefox?\'0px\':\'3px\', \'max-width\': \'calc(100% - 135px)\'}" type="range" min="0" max="{{duration}}" step="any" ng-model="currentTime" ng-change="userSeeked()" class="col-xs-0"/>\ <span style="margin: 2px 1px 2px 4px; padding: 0px !important; display: inline-block; float: left; text-align: center;">{{currentTime|secondsToTime}}&nbsp;/&nbsp;{{duration|secondsToTime}}</span>\ </div>\ </div>', }; }, ]) The template then has something like this in it: <custom-audio-controls audio-id="::('recording_'+recording.uniqueid+'_player')" have-file="recordingList.recordingFiles[recording.uniqueid]" file-loading="recordingList.fileLoading[recording.uniqueid]" style="margin-top: 3px;" autoplay></custom-audio-controls> <audio id="recording_{{::recording.uniqueid}}_player" ng-if="::recordingList.recordingFiles[recording.uniqueid]" src="data:{{::recordingList.recordingFiles[recording.uniqueid].mimetype}};base64,{{::recordingList.recordingFiles[recording.uniqueid].data}}"></audio>
To clarify the above code, I tried to get the duration with the delay, and few times more in interval, and it didn't help. I also tried (not in the code above) to set the src attribute using js instead of binding it in template, and the result was same.
One more thing, the bug has so far been detected on Debian and MacOS, haven't tried to run on other operating systems so far.

I met this too. here is the test case https://musicinformationretrieval.com/ipython_audio.html#Playing-Audio
Windows 10, Firefox 68.0.1 x64
Microsoft Edge show correct duration

Can the developers still see this bug report?

Severity: normal → S3

I’ve encountered the same behavior when using Base64-encoded audio sources in HTML5.
The playback works correctly, but the reported duration value (via audio.duration) is often extremely large (for example, 268435.453125), especially when the Data URI is long.

From my testing, this seems to happen because the audio metadata parser fails to correctly compute the duration when the entire file is embedded inline as a Base64 Data URI rather than fetched via a Blob or external URL. The decoder can still read and play the audio stream correctly, which is why playback works even though the duration value is invalid.

To reproduce or debug this more easily, you can generate Base64-encoded audio Data URIs directly in the browser here:
👉 https://www.base64converter.online

This makes it easier to test multiple encoding lengths and verify how browsers interpret duration depending on the Data URI size and type.
It looks like the issue might be in how duration is estimated before metadata parsing finishes, particularly for inline-encoded audio sources.

You need to log in before you can comment on or make changes to this bug.