I'm about to create a panoramic-360 video player with device orientation control for Android and IOS with cocoon.js and three.js.
I have successfully built the demo on the examples at threejs.org but struggling with a problem: My original test video file is 4000x1618 resolution and only 30sec long. Loading this makes audible voice but no picture. However if I try with a file converted down to 720x292, then it works perfectly fine on both android and ios! Unfortunately this lower res. video file is too poor quality, but if I try to load one any bigger, it will make only sound and no picture again.
I found these error logs in adb logcat when starting the movie:
E/OMXNodeInstance( 124): setParameter(4b:Nvidia.h264.decode, ParamPortDefinition(0x2000001)
W/ACodec ( 124): [OMX.Nvidia.h264.decode] setting nBufferCountActual to 13 failed: -1010
My code:
<!DOCTYPE html>
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
<script src='cordova.js'></script>
<script src='js/three.js'></script>
<script src='js/OrbitControls.js'></script>
<script src='js/PointerLockControls.js'></script>
<script src='js/DeviceOrientationControls.js'></script>
<script src='js/stats.min.js'></script>
<body style='margin: 0px;; overflow: hidden; text-align:center;'>
<div id="btn" style='background: red; width: 200px; height: 200px; position: absolute; z-index: 1000;' onclick="start_video()">START VIDEÓ</div>
<script>
var video = document.createElement('video');
video.loop = true;
video.src = 'heroes-new-720p.mp4';
function start_video() {
document.getElementById('btn').style.visibility = 'hidden';
video.play();
}
window.addEventListener('load', function() {
var renderer = new THREE.WebGLRenderer({
antialias : true,
});
renderer.setClearColor(new THREE.Color('lightgrey'), 1)
renderer.setSize( window.innerWidth, window.innerHeight );
document.body.appendChild( renderer.domElement );
var onRenderFcts= [];
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.01, 1000);
camera.position.z = 3;
var texture = new THREE.VideoTexture( video );
texture.minFilter = THREE.LinearFilter;
texture.format = THREE.RGBFormat;
texture.generateMipmaps = false;
var controls = new THREE.OrbitControls(camera)
controls.target.copy(scene.position)
function onDeviceOrientation(event){
if( !event.alpha ) return;
controls.enabled = false
controls = new THREE.DeviceOrientationControls(camera);
controls.connect();
window.removeEventListener('deviceorientation', onDeviceOrientation, false);
renderer.domElement.addEventListener('click', function(){
var domElement = renderer.domElement
if(domElement.requestFullscreen) domElement.requestFullscreen();
else if(domElement.msRequestFullscreen) domElement.msRequestFullscreen();
else if(domElement.mozRequestFullScreen) domElement.mozRequestFullScreen();
else if(domElement.webkitRequestFullscreen) domElement.webkitRequestFullscreen();
}, false);
}
window.addEventListener('deviceorientation', onDeviceOrientation, false);
onRenderFcts.push(function(){
controls.update()
})
;(function(){
var geometry = new THREE.SphereGeometry(10, 32, 16);
var material = new THREE.MeshBasicMaterial({
// opacity : 0.5,
// transparent : true,
// side : THREE.DoubleSide,
map: texture
});
var mesh = new THREE.Mesh( geometry, material );
mesh.scale.x = -1
scene.add( mesh );
})()
onRenderFcts.push(function(){
onWindowResize();
renderable();
})
function renderable() {
if ( video.readyState === video.HAVE_ENOUGH_DATA ) {
renderer.render( scene, camera );
}
}
function onWindowResize(){
renderer.setSize( window.innerWidth, window.innerHeight )
camera.aspect = window.innerWidth / window.innerHeight
camera.updateProjectionMatrix()
}
window.addEventListener('resize', onWindowResize, false)
// run the rendering loop
var lastTimeMsec= null
requestAnimationFrame(function animate(nowMsec){
// keep looping
requestAnimationFrame( animate );
// measure time
lastTimeMsec = lastTimeMsec || nowMsec-1000/60
var deltaMsec = Math.min(200, nowMsec - lastTimeMsec)
lastTimeMsec = nowMsec
// call each update function
onRenderFcts.forEach(function(onRenderFct){
onRenderFct(deltaMsec/1000, nowMsec/1000)
})
})
})
</script>
</body>
UPDATE: Since then I figured out that the problem is not the video resolution itself, but the dimensions. I created an 1920x1080 version of the original video that runs fine, so the only thing that bothers me is the quality. Even a 16000 kbps full HD video seems pretty pixelated on my Nexus 7 and ipad 4, I'm sure it should be nicer...
Well, finally I figured out that none of my tested devices (Moto G, Nexus 7, iPad4, HTC m8) can handle a video as texture in a three.js sphere that is larger then 1920x1080. I'm not sure about the reason but I found a very interesting article about resolutions for panoramic video, which enlighted the problem of the quality too.
To cut it short: in a 360 video, 2K width of a full HD video at 120 degrees become 682 pixels width, so this is the reason why I found my videos' quality unsatisfying.
Here's the article if anyone is interested:
http://www.360heros.com/2015/02/4k-vr-360-video-what-is-it-and-how-can-i-produce-it/