Titanium JIRA Archive
Titanium SDK/CLI (TIMOB)

[TIMOB-25801] iOS: SDK 7.0.2.GA - VideoPlayer playback issues

GitHub Issuen/a
TypeBug
PriorityHigh
StatusClosed
ResolutionFixed
Resolution Date2018-05-21T11:29:16.000+0000
Affected Version/sRelease 7.0.2
Fix Version/sRelease 7.3.0
ComponentsiOS
Labelsios, videoplayer
ReporterHenry Orozco
AssigneeVijay Singh
Created2018-02-22T19:25:15.000+0000
Updated2018-07-11T17:23:28.000+0000

Description

on iOS titanium SDK 7.0.2.GA once the playback is done the media player fires the complete event (which is correct) and then the stop event (which shouldn't) also the function stop doesn't stop the playback but rather pauses it. and the durationavailable event is fired twice the second time an audio/video is played.
// Some comments here
var dataStructure = [{
  title : "Play video",
  action : playVideo
}, {
  title : "Pause video",
  action : pauseVideo
}, {
  title : "Stop video",
  action : stopVideo
}, {
  title : "Change video source (local)",
  action : changeLocalVideoSource
}, {
  title : "Change video source (remote)",
  action : changeRemoteImageSource
}, {
  title : "Change background color",
  action : changeBackgroundColor
}, {
  title : "Set volume to 50%",
  action : decreaseVolume
}, {
  title : "Get playable duration",
  action : getPlayableDuration
}, {
  title : "Get playback state",
  action : getPlaybackState
}, {
  title : "Set full width",
  action : setSizeFullWidth
}, {
  title : "Set width + height to 300 (animated)",
  action : setSize
}, {
  title : "Is playing?",
  action : getPlaying
}, {
  title: "Take screenshot at 5s",
  action: takeScreenshot
}, {
  title: "Take multiple screenshots",
  action: captureSeriesOfImages
}, {
  title: "Set initial playback time",
  action: setInitialPlaybackTime
}, {
  title: "Is airPlay allowed?",
  action: getAllowsAirPlay
}, {
  title: "repeatMode ?",
  action: getRepeatMode
}];
 
 
var counter = 0;
 
/* -- UI -- */
 
var isiOS = (Ti.Platform.osname == "ipad" || Ti.Platform.osname == "iphone");
 
var win = Titanium.UI.createWindow({
  title : 'Video Player Demo',
  backgroundColor : '#fff',
  layout : 'vertical'
});
 
var nav = isiOS ? Ti.UI.iOS.createNavigationWindow({
  window : win
}) : null;
 
var header = Ti.UI.createView({
  height : 350,
  backgroundColor : "#eee"
});
 
var content = Ti.UI.createScrollView({
  layout : "horizontal",
  scrollType : "vertical",
  contentWidth : Ti.Platform.displayCaps.platformWidth,
  contentHeight : 1000
});
 
var videoPlayer = Titanium.Media.createVideoPlayer({
  autoplay : true,
  url : 'http://techslides.com/demos/sample-videos/small.mp4',//'movie.mp4',
  initialPlaybackTime: 1000,
  showsControls:true,
  pictureInPictureEnabled : true, // Only supported on iOS9 & iPad Air or later!
  scalingMode : Titanium.Media.VIDEO_SCALING_MODE_RESIZE_ASPECT,
  repeatMode : Titanium.Media.VIDEO_REPEAT_MODE_NONE
});
 
for (var i = 0; i < dataStructure.length; i++) {
  var btn = Ti.UI.createButton({
    top : '3.5%',
    left : '7%',
    height : 60,
    width : '40%',
    tintColor : '#b50d00',
    backgroundColor : '#e0e0e0',
    title : dataStructure[i].title
  });
 
  btn.addEventListener("click", dataStructure[i].action);
 
  content.add(btn);
}
 
header.add(videoPlayer);
win.add(header);
win.add(content);
 
if (nav) {
  nav.open();
} else {
  win.open();
}
 
/* -- Events -- */
 
videoPlayer.addEventListener('complete', function(e) {
  Ti.API.info('complete ' + JSON.stringify(e));
});
 
videoPlayer.addEventListener('durationavailable', function(e) {
  Ti.API.info('durationavailable ' + JSON.stringify(e));
});
 
videoPlayer.addEventListener('error', function(e) {
  Ti.API.info('error ' + JSON.stringify(e));
});
 
videoPlayer.addEventListener('load', function(e) {
  Ti.API.info('load ' + JSON.stringify(e));
});
 
videoPlayer.addEventListener('playbackstate', function(e) {
  Ti.API.info('playbackstate ' + JSON.stringify(e));
});
 
videoPlayer.addEventListener('playing', function(e) {
  Ti.API.info('playing ' + JSON.stringify(e));
});
 
videoPlayer.addEventListener('preload', function(e) {
  Ti.API.info('preload ' + JSON.stringify(e));
});

Comments

  1. Hans Knöchel 2018-02-23

    Hey [~horozco], it looks like your sample is missing the functions it calls, e.g. playVideo. *EDIT*: I assume you got the test-case from a different ticket. Here is the full case:
       
       /* -- Data Structure -- */
       
       var dataStructure = [{
       	title : "Play video",
       	action : playVideo
       }, {
       	title : "Pause video",
       	action : pauseVideo
       }, {
       	title : "Stop video",
       	action : stopVideo
       }, {
       	title : "Change video source (local)",
       	action : changeLocalVideoSource
       }];
       
       /* -- UI -- */
       
       var isiOS = (Ti.Platform.osname == "ipad" || Ti.Platform.osname == "iphone");
       
       var win = Titanium.UI.createWindow({
       	title : 'Video View Demo',
       	backgroundColor : '#fff',
       	layout : 'vertical'
       });
       
       var nav = isiOS ? Ti.UI.iOS.createNavigationWindow({
       	window : win
       }) : null;
       
       var header = Ti.UI.createView({
       	height : 350,
       	backgroundColor : "#eee"
       });
       
       var content = Ti.UI.createScrollView({
       	layout : "vertical"
       });
       
       var videoPlayer = Titanium.Media.createVideoPlayer({
       	autoplay : true,
       	backgroundColor : 'blue',
       	height : 300,
       	width : 300,
       	url : 'movie.mp4',
       	mediaControlStyle : Titanium.Media.VIDEO_CONTROL_DEFAULT,
       	scalingMode : Titanium.Media.VIDEO_SCALING_ASPECT_FIT
       });
       
       for (var i = 0; i < dataStructure.length; i++) {
       	var btn = Ti.UI.createButton({
       		top : 20,
       		width : '80%',
       		height : '10%',
       		backgroundColor : '#ddd',
       		borderRadius : '5%',
       		title : dataStructure[i].title
       	});
       
       	btn.addEventListener("click", dataStructure[i].action);
       
       	content.add(btn);
       }
       
       header.add(videoPlayer);
       win.add(header);
       win.add(content);
       
       if (nav) {
       	nav.open();
       } else {
       	win.open();
       }
       
       /* -- Events -- */
       
       videoPlayer.addEventListener('complete', function(e) {
       	Ti.API.info('complete ' + JSON.stringify(e));
       });
       
       videoPlayer.addEventListener('durationavailable', function(e) {
       	Ti.API.info('durationavailable ' + JSON.stringify(e));
       });
       
       videoPlayer.addEventListener('error', function(e) {
       	Ti.API.info('error ' + JSON.stringify(e));
       });
       
       videoPlayer.addEventListener('load', function(e) {
       	Ti.API.info('load ' + JSON.stringify(e));
       });
       
       videoPlayer.addEventListener('playbackstate', function(e) {
       	Ti.API.info('playbackstate ' + JSON.stringify(e));
       });
       
       videoPlayer.addEventListener('playing', function(e) {
       	Ti.API.info('playing ' + JSON.stringify(e));
       });
       
       videoPlayer.addEventListener('preload', function(e) {
       	Ti.API.info('preload ' + JSON.stringify(e));
       });
       
       /* -- Actions -- */
       
       function playVideo() {
       	videoPlayer.play();
       }
       
       function pauseVideo() {
       	videoPlayer.pause();
       }
       
       function stopVideo() {
       	videoPlayer.stop();
       }
       
       function changeLocalVideoSource() {
       	videoPlayer.url = 'another.mp4';
       	videoPlayer.play();	
       }
       
    Regarding your report: - "stop" event fired once the movie completes: -Need to investigate- There is no "stop" event, what do you mean? - The function "stop()" doesn't stop the playback but rather pauses it: Cannot reproduce, it stops correctly - The "durationavailable" event is fired twice the second time a video is played: Cannot reproduce, it only fires once (before the video starts). It uses the native KVO on player.currentItem.duration which isn't handled separately. If you can provide a test case for that, let me know!
  2. Henry Orozco 2018-02-23

    Hey Hans, here is a complete and functional test case: Steps to reproduce the errors: * once the movie completes, the complete event is fired (correct) along with the playbackstate event with the e.playbackState == Ti.Media.VIDEO_PLAYBACK_STATE_STOPPED (incorrect) * When the video is playing and click 'Stop video' then click 'Get playback state' you will see that it is actually paused not stopped, is not just the playbackstate flag that's incorrect it is actually not stopping the playback. * durationavailable event gets fired twice, click 'change source' to play a differente media file and look at the console you will see that durationavailable was fired twice. Thanks!
       var dataStructure = [{
         title : "Play video",
         action : playVideo
       }, {
         title : "Pause video",
         action : pauseVideo
       }, {
         title : "Stop video",
         action : stopVideo
       },{
         title : "Change source",
         action : changeRemoteSource
       },{
         title : "Change source 2",
         action : changeRemoteSourceTwo
       },{
         title : "Get playback state",
         action : getPlaybackState
       }];
        
       /* -- UI -- */
        
       var isiOS = (Ti.Platform.osname == "ipad" || Ti.Platform.osname == "iphone");
        
       var win = Titanium.UI.createWindow({
         title : 'Video Player Demo',
         backgroundColor : '#fff',
         layout : 'vertical'
       });
        
       var nav = isiOS ? Ti.UI.iOS.createNavigationWindow({
         window : win
       }) : null;
        
       var header = Ti.UI.createView({
         height : 350,
         backgroundColor : "#eee"
       });
        
       var content = Ti.UI.createScrollView({
         layout : "horizontal",
         scrollType : "vertical",
         contentWidth : Ti.Platform.displayCaps.platformWidth,
         contentHeight : 1000
       });
        
       var videoPlayer = Titanium.Media.createVideoPlayer({
         autoplay : true,
         url : 'http://techslides.com/demos/sample-videos/small.mp4',//'movie.mp4',
         initialPlaybackTime: 1000,
         showsControls:true,
         pictureInPictureEnabled : true, // Only supported on iOS9 & iPad Air or later!
         scalingMode : Titanium.Media.VIDEO_SCALING_MODE_RESIZE_ASPECT,
         repeatMode : Titanium.Media.VIDEO_REPEAT_MODE_NONE
       });
        
       for (var i = 0; i < dataStructure.length; i++) {
         var btn = Ti.UI.createButton({
           top : '3.5%',
           left : '7%',
           height : 60,
           width : '40%',
           tintColor : '#b50d00',
           backgroundColor : '#e0e0e0',
           title : dataStructure[i].title
         });
        
         btn.addEventListener("click", dataStructure[i].action);
        
         content.add(btn);
       }
        
       header.add(videoPlayer);
       win.add(header);
       win.add(content);
        
       if (nav) {
         nav.open();
       } else {
         win.open();
       }
        
       /* -- Events -- */
        
       videoPlayer.addEventListener('complete', function(e) {
         Ti.API.info('complete ' + JSON.stringify(e));
       });
        
       videoPlayer.addEventListener('durationavailable', function(e) {
         Ti.API.info('durationavailable ----------------------->' + JSON.stringify(e));
       });
        
       videoPlayer.addEventListener('error', function(e) {
         Ti.API.info('error ' + JSON.stringify(e));
       });
        
       videoPlayer.addEventListener('load', function(e) {
         Ti.API.info('load ' + JSON.stringify(e));
       });
        
       videoPlayer.addEventListener('playbackstate', function(e) {
         Ti.API.info('playbackstate ' + JSON.stringify(e));
         if ( e.playbackState === Ti.Media.VIDEO_PLAYBACK_STATE_STOPPED ) {
       		alert('stopped');
       	}
       });
        
       videoPlayer.addEventListener('playing', function(e) {
         Ti.API.info('playing ' + JSON.stringify(e));
       });
        
       videoPlayer.addEventListener('preload', function(e) {
         Ti.API.info('preload ' + JSON.stringify(e));
       });
        
       /* -- Actions -- */
        
       function playVideo() {
         videoPlayer.play();
       }
        
       function pauseVideo() {
         videoPlayer.pause();
       }
        
       function stopVideo() {
         videoPlayer.stop();
       }
        
       function changeRemoteSource() {
       	videoPlayer.stop();
       	// audioverse.org media file
         videoPlayer.url = 'https://d31oibm4w2hgxr.cloudfront.net/2016/11/15565/_narr_AuntSueUncleDan-ThePersecutorFromTarsus-80k.mp3';
         videoPlayer.play();
       }
       
       function changeRemoteSourceTwo() {
       	videoPlayer.stop();
       	// audioverse.org media file
         videoPlayer.url = 'https://d31oibm4w2hgxr.cloudfront.net/2017/06/16552/JeanBoonstra_narr_JeanBoonstra-Episode01-Di...yMountain,Season1-96k.mp3';
         videoPlayer.play();
       }
        
        function getShowControls() {
         alert(videoPlayer.showsControls);
       }
        
       function getPlaybackState() {
       	if ( videoPlayer.playbackState == Ti.Media.VIDEO_PLAYBACK_STATE_PLAYING)
       		alert('Playing');
       	else if ( videoPlayer.playbackState == Ti.Media.VIDEO_PLAYBACK_STATE_PAUSED)
       		alert('Paused');
       	else if ( videoPlayer.playbackState == Ti.Media.VIDEO_PLAYBACK_STATE_STOPPED)
       		alert('Stopped');
       	else
         	alert(videoPlayer.playbackState);
       }
       
  3. Vijay Singh 2018-02-27

    I verified this test case in previous implementation of TiMediaVideoPlayer where we were using MPMoviePlayerViewController (SDK 6.3.0) and found following result in context of above 3 points - 1. Once movie completes, its playback state event gives Ti.Media.VIDEO_PLAYBACK_STATE_PAUSED. 2. When the video is playing and click 'Stop video' then click 'Get playback state' it gives Ti.Media.VIDEO_PLAYBACK_STATE_STOPPED. 3. If we click 'change source' to play a differente media file, durationavailable event gets fired twice. And in native app also, it get fired twice. So we have to fix point 1 and point 2. Point 3 is expected behaviour.
  4. Vijay Singh 2018-02-28

    PR - https://github.com/appcelerator/titanium_mobile/pull/9890
  5. Henry Orozco 2018-02-28

    Vijay Singh is not just the playbackstate value that's incorrect it is actually not stopping the playback. if you call stop() it's not stopping the playback. The playbackstate value is useful to have it correct but it's more important that the playback is actually stopped.
  6. Vijay Singh 2018-03-01

    [~horozco] Do you mean, after clicking stop button movie continues to play? I can see when I click stop button, it comes to starting position of video and it's not playing. And in AVPlayerViewController, apple has not given stop api. So this is achieved in SDK by setting seekToTime to zero with pause for backward compatibility.
  7. Henry Orozco 2018-03-01

    What I meant is after you stop the movie, and you play another one (click change source) until then the playbackstate event (STOPPED) is fired:
       videoPlayer.addEventListener('playbackstate', function(e) {
         Ti.API.info('playbackstate ' + JSON.stringify(e));
         if ( e.playbackState === Ti.Media.VIDEO_PLAYBACK_STATE_STOPPED ) {
       		alert('stopped');
       	}
       });
       
    That sometimes causes to the new movie playing to stop. In other words, after you stop a movie and then change the source to play a new movie (videoPlayer.setUrl('newMovie.mp4'); videoPlayer.Play() ) until then the previous movie is actually stopped and the playbackstate event STOPPED is fired. For some reason, the VIdeoPlayer waits until a new movie is setted and played to acutally stop the previous one and since the stop functionality it's executed until that point it causes to stop the current movie. Hope that helps. The best.
  8. Vijay Singh 2018-03-14

    [~horozco] I am not able to reproduce the issue, which you are mentioning, using this PR. [~ewieber] Can you please verify while testing it? Thanks.
  9. Samir Mohammed 2018-07-11

    *Closing ticket.* Verified fix in SDK Version: 7.3.0.v20180628132121 Able to follow and complete the steps above mentioned above by [~vijaysingh] *Test Environment*
       APPC Studio: 5.0.0.201712081732
       APPC CLI: 7.0.4
       iPhone 6 simulator (11.2)
       Operating System Name: Mac OS High Sierra
       Operating System Version: 10.13
       Node.js Version: 8.9.1
       Xcode 9.2
       

JSON Source