I have been fighting with a hard to find bug for awhile now. I have an intro cutscene that plays on app startup the first time. You can tap to skip past it.
This has worked great for a long time, but I have always had a couple reports where people were claiming that the game would freeze on startup.
This was only for Android 4.x devices and when I told them to try and tap past it that worked fine but the video never played, it would just load up and stop with a blank screen. These users (being early adoptors of 4.x) were generally using some crazy combination of roms and launchers and other customizations so I just thought it was probably that.
Up until recently my 4.x usage was in the single digits, but we have gone up to about 40% 4.x devices now and I am getting lots more of these reports. I finally got ahold of a nexus 7 so that I could properly test it (I only have a couple devices in house).
It turns out that the problem is that Handheld.PlayFullScreenMovie works exactly like you expect on the older devices (my galaxy tab with 2.x works just fine) once you call Handheld.PlayFullScreenMovie unity pauses immediately and the movie loads and when it is done unity resumes.
However, on newer devices this is not the case. Unity does NOT pause right away, but instead happily runs in the background while the movie is loaded into memory, and only once it starts playing does Unity pause. This can take up to 1.5 seconds (tho it is usually about 0.6ish on my nook and nexus 7.)
So before I had code that ran like this:
Debug.Log("Movie Starts");
if (horizontalIntro) Screen.orientation = ScreenOrientation.LandscapeLeft;
Handheld.PlayFullScreenMovie (moviePath, Color.black, FullScreenMovieControlMode.CancelOnInput);
if (horizontalIntro) Screen.orientation = ScreenOrientation.Portrait;
Debug.Log("Movie Ends");
// now load the main menu
Instantiate(sceneLoader);
and that worked great. The log would show ’ Movie starts’ an then the movie would play and when it was done the log would show ‘Movie ends’ and everything was dandy.
On newer devices, the log shows ‘Movie starts’, and then ‘movie ends’ and then it happily instantiates my scene loader which does it’s thing and then at some point after that Unity will actually pause, and for some reason the movie would sometimes just hang. It would usually work whenever I did a build and run from unity, but then not work when I ran it from the device by tapping the app. (but sometimes it would work. It sucked)
So I tried this:
Debug.Log("Movie Starts");
if (horizontalIntro) Screen.orientation = ScreenOrientation.LandscapeLeft;
Handheld.PlayFullScreenMovie (moviePath, Color.black, FullScreenMovieControlMode.CancelOnInput);
if (horizontalIntro) Screen.orientation = ScreenOrientation.Portrait;
Debug.Log("Movie Ends");
// kill some time
for (int i = 0; i < 100; i++) {
Debug.Log ("--> What time do we think it is? " + Time.timeSinceLevelLoad);
Debug.Log ("--> What time is it really? "+ Time.realtimeSinceStartup);
yield return new WaitForSeconds(0.1f);
}
// now load the main menu
Instantiate(sceneLoader);
This will generate some log output like this:
I/Unity ( 2820):
I/Unity ( 2820): (Filename: ./Runtime/ExportGenerated/AndroidManaged/UnityEngineDebug.cpp Line: 43)
I/Unity ( 2820):
I/Unity ( 2820): --> What time do we think it is? 1
I/Unity ( 2820):
I/Unity ( 2820): (Filename: ./Runtime/ExportGenerated/AndroidManaged/UnityEngineDebug.cpp Line: 43)
I/Unity ( 2820):
I/Unity ( 2820): --> What time is it really? 1.154128
I/Unity ( 2820):
I/Unity ( 2820): (Filename: ./Runtime/ExportGenerated/AndroidManaged/UnityEngineDebug.cpp Line: 43)
I/Unity ( 2820):
I/Unity ( 2820): --> What time do we think it is? 1.188923
I/Unity ( 2820):
I/Unity ( 2820): (Filename: ./Runtime/ExportGenerated/AndroidManaged/UnityEngineDebug.cpp Line: 43)
I/Unity ( 2820):
I/Unity ( 2820): --> What time is it really? 1.180601
I/Unity ( 2820):
...
...
... this goes on for a bit
...
...
I/Unity ( 2820): (Filename: ./Runtime/ExportGenerated/AndroidManaged/UnityEngineDebug.cpp Line: 43)
I/Unity ( 2820):
I/Unity ( 2820): --> What time do we think it is? 1.745316
I/Unity ( 2820):
I/Unity ( 2820): (Filename: ./Runtime/ExportGenerated/AndroidManaged/UnityEngineDebug.cpp Line: 43)
I/Unity ( 2820):
I/Unity ( 2820): --> What time is it really? 1.737076
I/Unity ( 2820):
I/Unity ( 2820): (Filename: ./Runtime/ExportGenerated/AndroidManaged/UnityEngineDebug.cpp Line: 43)
I/Unity ( 2820):
W/ActivityManager( 357): Activity pause timeout for ActivityRecord{426adb48 au.com.tinmangames.gamebook1android/com.unity3d.player.UnityPlayerNativeActivity}
I/Unity ( 2820): --> What time do we think it is? 1.860899
I/Unity ( 2820):
I/Unity ( 2820): (Filename: ./Runtime/ExportGenerated/AndroidManaged/UnityEngineDebug.cpp Line: 43)
I/Unity ( 2820):
I/Unity ( 2820): --> What time is it really? 1.852489
I/Unity ( 2820):
I/Unity ( 2820): (Filename: ./Runtime/ExportGenerated/AndroidManaged/UnityEngineDebug.cpp Line: 43)
I/Unity ( 2820):
and then it will finally pause the engine, the video will play. When it is done you will come back with something like this:
I/Unity ( 2820): --> What time do we think it is? 1.961225
I/Unity ( 2820):
I/Unity ( 2820): (Filename: ./Runtime/ExportGenerated/AndroidManaged/UnityEngineDebug.cpp Line: 43)
I/Unity ( 2820):
I/Unity ( 2820): --> What time is it really? 31.31148
I/Unity ( 2820):
I/Unity ( 2820): (Filename: ./Runtime/ExportGenerated/AndroidManaged/UnityEngineDebug.cpp Line: 43)
So, it takes the unity engine about 0.7 seconds from when you tell it to start the movie to when it actually starts the movie. This happens on my Nexus 7 with android 4.1.1 as well as my Nook Tablet with whatever the hell OS is on that thing.
So now I have implemented this terrible hack (that seems to give consistent playback on all devices now)
Debug.Log("---> In Android Loop");
float currentTime = Time.timeSinceLevelLoad;
float timeDifference = Time.realtimeSinceStartup - currentTime;
if (horizontalIntro) Screen.orientation = ScreenOrientation.LandscapeLeft;
Debug.Log ("--> start movie " + Time.realtimeSinceStartup);
Handheld.PlayFullScreenMovie (moviePath, Color.black, FullScreenMovieControlMode.CancelOnInput);
currentTime = Time.timeSinceLevelLoad + timeDifference; // in theory this should be a few seconds off of the real time by now
Debug.Log ("--> movie ended? " + currentTime + " " + Time.realtimeSinceStartup);
if (Mathf.Abs (currentTime - Time.realtimeSinceStartup) < 0.3f) {
// then we didnt actually pause anything.
// start counting down until Unity is paused
for (int i = 0; i < 100; i++) {
currentTime = Time.timeSinceLevelLoad + timeDifference; // if we are not paused then this should be close to the real time
if (Mathf.Abs(currentTime - Time.realtimeSinceStartup) < 0.3f) {
yield return new WaitForSeconds(0.1f);
} else {
Debug.Log ("--> We did pause!");
break;
}
}
}
Debug.Log ("--> Movie Actually Ended.");
finalizeVideoPlay();
What the hell am I doing here?
I am using the timeSinceLevelLoad and comparing that with the realtimeSinceStartup (with an adjustment to get them to be closer together)
As we can see from the test above, when the Unity engine actually pauses, then the timeSinceLevelLoad will also pause, but the realtimeSinceStartup will not. So in theory, if the video actually pauses the engine, then the two time stamps should be very different. If they are not very different then that means that Unity never got paused and we need to twiddle our thumbs until it actually does get paused at which point we continue on our merry way.
note: this runs from a coroutine, but the not-pausing problem happens no matter where you call the Handheld.PlayFullScreenMovie from.
From this pain I hope one (or more) of three things happens:
-
that this saves someone else a huge headache of figuring out what the hell is up with your video.
-
that Unity fixes it and perhaps forces an engine pause as soon as Handheld.PlayFullScreenMovie is called instead of waiting for the OS to tell it to pause.
or 3) that I am completely insane, and this is somehow tied into the screen orientation, or there is a far better way to deal with this or some other thing, and I don’t have to put this horrible horrible code into my project.
Cheers!
-Ben