Tag Archives: Android

Playing Audio in Android/iOS Web Apps

11 Sep 2012

If you’re familiar with the state of HTML5 in Android land you know that many CSS3 features are not supported, among them is the AUDIO tag. There is a library out called SoundManager2 that claims to offer a “reliable cross-platform audio” solution but after trying it on mobile platforms I think it falls short of that promise.

SoundManager 2’s approach is to use HTML5 where possible with Flash as a fallback mechanism. Makes sense for desktop OS’s, not so much for mobile. iOS, even with its own quirks (no inline playing, no autoplay, etc) has functional HTML5 audio/video so that’s not much of an issue. Android is very different in that the HTML5 AUDIO tag is not implemented. Flash exists on Android but that has its own issues, among them: we are forced to assume that the Flash Player exists on the Android device in the first place which isn’t always the case. Add to that the fact that many users have to install the Flash Player manually on their devices as there is an absence of any auto-install capability. The final nail in that coffin is Adobe’s abandonment of the Android Flash Player altogether meaning no one can install it.

SoundManager 2 can no longer claim to be useful for mobile when its completely unusable on Android. Maybe when a version of Android with HTML5 Audio support takes the lion’s share of the Android ecosystem SoundManager 2 may again have use within the mobile space.

In any event, as mentioned, the lack of HTML5 AUDIO tag support continues to make supporting sound in Android Web Apps a challenge. The solution I’ve come up with (not that its a novel one) is to use the HTML5 VIDEO tag instead. VIDEO has support at least back to Android 2.2. Most events are supported though some don’t do quite behave in the manner that you would like for them to which makes the creation of a custom audio player somewhat of a challenge. In the end you may just have to bail on some lofty ideas/features that you may have for your own custom player but at least you can still get some sound working reliably.

It has been said that you can in fact feed the VIDEO tag an MP3. However, what happens within Android differs across its varied flavors. In Android 2.2 what will happen is that Android will not play the audio in-page but instead launch the native media player – and what you have is a black screen, devoid of controls albeit with your MP3 playing. In Android 4 you can get MP3 audio to work inline.

Due to the different behaviors and presentations between Android versions I’ve personally switched to using video files(mp4) to deliver sound. The result is that in Android 2.2 the native media player will display the video content – an image keyframed every second – which satisfies some mystery requirement that allows the on-screen controls to appear. This is much better than a black screen. On Android 4 we don’t care about the visual aspect of the video because my implementation avoids it entirely.

The concept is to use single VIDEO tag that is placed off-screen, use Javascript to change its src and control the sound via the relevant HTML5 media events.

It is important to note that some VIDEO tag attributes are necessary for this to work as without them you’ll be greeted by silence. Below is the HTML to use:

...
<video id="videoEl" autobuffer height="10" width="10" controls style="position:absolute;top:0px;left:-99px;">
  <source src="something.mp4">
</video>
...

The autobuffer is there because it can’t hurt. The one thing that is necessary is the presence of the video controls (yes, even though you won’t see them). Otherwise this is all pretty simple – you can see that the VIDEO tag is placed off screen.

I won’t post my complete setup as thats a bit of code, however, here’s a great start; first some HTML to be styled however you like:

...
<ul id="songs" class="music_list">
  <li src="vid/song_1.mp4">This is song number one</li>
  <li src="vid/song_2.mp4">This is the second song</li>
  <li src="vid/song_3.mmp4">Number three</li>
  <li src="vid/song_4.mp4">The last song</li>
  <li qwe="noSound">Just a list item, no video/sound to play here</li>
</ul>
...

In this case I’ve elected to keep the URL to the video in the LI tag. Here’s my JavaScript that utilizes the above, populates the VIDEO src, and controls the VIDEO (our sound):

...
var videoPlayer = (function(){
  var _isPlaying = false;
  var _elTarget;
  var _video;
  function _resetAllVideoStyles(){
    var lists = document.getElementsByTagName('ul');
    for (var i=0;i<lists.length;i++){
      if (lists[i].getAttribute('class').indexOf('music_list') != -1){
        var desc = lists[i].childNodes;
        for (var j=0;j<desc.length;j++){
          if (desc[j].nodeType == 1){
            desc[j].setAttribute('class','');
          }
        }
      }
    }
  }
  function _ifOldAndroid(){
    return navigator.userAgent.indexOf('Android 2.3.3') != -1 ? true : false;
  }
  function _loadVideo(t,str){
    t.setAttribute('class','mp_selected');
    _video.src = str;
    _elTarget = t;
    _video.load();
    setTimeout(function(){
      _video.play();
      _isPlaying = true;
    },1000); // NECESSARY timeout, play too soon and nothing will happen
  }
  function _stopVideo(){
    _video.pause();
    _isPlaying = false;
    _resetAllVideoStyles();
  }
  function init(v_id){;
    var el = _video = document.getElementById(v_id);
    el.addEventListener('error',function(){
      console.log('Cannot load the file');
    });
    el.addEventListener('pause', function(){
      console.log('paused/stopped');
    });
    el.addEventListener('loadstart', function(){
      console.log('starting to lo
    });
    el.addEventListener('play', function(){
      console.log('playing');
    });
    el.addEventListener('durationc
      console.log('duration changed');
    });
    el.addEventListener('progress', function(){
      console.log('progress: file is being downloaded');
    });
  }
  function dispatcher(t,str){
    if (t == _elTarget){
      _elTarget = null;
      _stopVideo();
    } else if (_isPlaying){
      _stopVideo();
      setTimeout(function(){
        _loadVideo(t,str);
      },100);
    } else {
      _loadVideo(t,str);
    }
  }
	
  return{
    init:init,
    dispatcher:dispatcher
  }
})();
window.addEventListener('load',function(){
  var list = document.getElementsByTagName('li');
  for (var i=0;i < list.length;i++){
    if (list[i].getAttribute('qwe') !== 'noSound'){
      list[i].addEventListener('click',function(e){
        videoPlayer.dispatcher(e.target,e.target.attributes[0].nodeValue);
      },false);
    }
  }
},false);
...

As you can see events are bound to all qualifying LI tags – the qualifier being that the parent UL must have a class called “music_list” assigned to it.

Thats a decent start for you – all the events are setup – you will quickly see that they don’t really fire when you expect them to. For example, the onplay event seems to start as the video is buffering, not when the video actually starts to play. ondurationchange may fire more than once – etc…. so as i mentioned before making a slick UI will be a bit of a chore. One thing that should be useful, though granted maybe not a cureall is the video.paused property. At least you will know when the video is playing or not, but as I already noted the video is considered playing when what I believe based on observation is buffering and not actual playing.

As far as iOS the video does not play inline, rather, the native video player will come up full screen. This happens because contrary to every other mobile browser Apple has decided in its totalitarian wisdom to buck the standard and suspend the mobile app in favor of its built-in media player. That’s a rather ironic tactic IMO as they profess to be on the side of HTML 5 & standardization but as usual they change the game per their whim and there’s nothing that can be done about it. Apple gives no explanation for this so it is what it is.

So that’s about – doing the above gets you something that works on both major mobile OS’s – Android with its caveats, and iOS with its mobile media player hijacking the

Losing Device Connection in Eclipse

20 Jun 2012

There may come a time where you will encounter an “AdbCommandRejectedException error: device not found” while trying to upload your app to your device from within Eclipse. This message will appear in the console and will be accompanied by this text: “Error during Sync: An established connection was aborted by the software in your host machine”. As a result you will be unable to install your app. Opening DDMS will show that the device is not connected to your computer a it simply doesn’t appear in the device list even though the phone is in fact physically connected.

To further illustrate:

To resolve this without restarting Eclipse you can execute a BAT file that will restart the ADB server. To do this navigate to your Android installation folder and locate the “platform-tools” folder. Within that folder create an appropriately named BAT file, for example, “restart-adb.bat”. Edit the newly created file in a text editor such as Notepad and add the following two lines:

adb kill-server && adb start-server
pause

Save and close. TO restart ADB simply double-click your bat file and the server will be restarted. A command prompt will appear informing you of what is happening:

Thats it, the ADB has been restarted.

Solving “The connection to the server was unsuccessful” Error in Android/PhoneGap

10 Apr 2012

At some point in time you will always have to update your dev environment – and inevitably when doing so you will find that you can’t compile your apps any more for any sort of a number of reasons – doesn’t matter if its iOS or Android (though I find its much, much more common on iOS). Well, today I’ve updated my Android SDK since I’d like for the first time ever to be able to use the AVD as Google has improved performance in that area. However, upon compiling one of my apps I have run into this error:

...
04-10 10:46:13.672: I/System.out(4057): onReceivedError: Error code=-6 Description=The connection to the server was unsuccessful. URL=file:///android_asset/www/index.html
...

This can be solved by adding a line to your main activity.java file:

...
super.setIntegerProperty("loadUrlTimeoutValue", 7000);
...

The timeout value itself is arbitrary, here I have it at 7 seconds. Adding this one line above the super.loadUrl resolves the “Connection to the server was unsuccessful” error.

Also, check to make sure that any remote scripts such as those being delivered to the app from a server are accessible. Sometimes I forget to remove the remote debugger from a project and find that this error will appear because the script cannot be downloaded – in that case you see the error message appear while the app loads behind it. So, check your script tags for remotely loading scripts and remove any hooks to remote debuggers/testing servers. You should also download copies of javascript libraries and keep them within your app to remove or at least reduce the chances of this error appearing.

Changing Target Android Version in Eclipse

12 Mar 2012

This one takes care of the “Attribute minSdkVersion(x) is lower that the project target API level(x)” error that you may encounter if attmpeting to change your app’s Android target version after having setup your Eclipse project for Phonegap. The error manifests itself as a build error with this the following description (which illustrates a target of 8 being below the projects API level of 10):

To change the target Android API level you need to do two things:

  1. Update the Android Manifest
  2. Update the App Target API Level

See this page for more information on Build Targets and
acceptable values: Android API Level Guide.

First go to your project folder in Eclipse. In the root you should find a file called “AndroidManifest.xml“. Double-click this file. Eclipse will either show you the UI view of the XML document, which looks like this:

Or Eclipse may show you the manifest in source view which would look like this:

In the UI view of the manifest you are looking for the “Min SDK version” filed – set that to the desired SDK version that you want to target. Save the change and then close the manifest. If you cannot locate the Min SDK Version then look under “Manifest Extras” and click on “Uses SDK” – the desired field will appear to the right under the “Attributes for Uses SDK” heading.

If you see the source-code view for the manifest then look for this line:

...

...

… and simply change the number (“8” in the above example) to whichever target API Level that you’re going for.

Next, you need to change the Project Build Target. To do so you will locate the root of your project within the Project Explorer (this appears as a directory tree structure), right click the app project root and select “Properties“. You will see the properties panel which looks like this:

At this point simply select the proper build target, apply and close.

Set App Scaling in PhoneGap, Android

10 Feb 2012

One of the very first things any Android PhoneGap developer will run into is setting the appview scale to set the proper view size and prevent “zooming” within your app. The “zoom” could happen if the user taps a text field or pinches the screen. We want the scale set appropriately and zooming disabled.

Open up your main activity.java (located in your “src” directory) and compare it with what I have below:

package com.rickluna.myapp;

import com.phonegap.*;
import android.os.Bundle;
import android.view.WindowManager;
import android.webkit.WebSettings;

public class myAppActivity extends DroidGap {
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        super.loadUrl("file:///android_asset/www/index.html");

        /* lock scaling in android 2.2 code to be pasted right here */

    }
}

The fix for this is easy and tested on Android 2.2, Android 2.3.3, and Android 3.1:

...
        /* lock scaling in android 2.2 */
        appView.getSettings().setSupportZoom(false);
        appView.getSettings().setUseWideViewPort(false);
        WebSettings ws = appView.getSettings();
        ws.setDefaultZoom(WebSettings.ZoomDensity.MEDIUM);
        appView.getSettings().setDefaultZoom(WebSettings.ZoomDensity.MEDIUM);
        appView.setInitialScale(0);
        ws.setSupportZoom(false);
        ws.setBuiltInZoomControls(false);
        ws.setUseWideViewPort(false);
...

The thing that you may find good to know is that you can specify the zoom density to whatever you like. I find that MEDIUM and initialScale of “0” gives me something that looks great across Android phones and looks consistent with what happens on iPhones. That being said I have used FAR on one occasion with initialScale set to “100”.

According to the android docs FAR should have an initial scale of “100” and MEDIUM should be “150”, however, according to my testing, you want an initialScale of 0 for MEDIUM. The reason is that in Android 2.3.6 (which many current phones are running such as the popular Samsung Galaxy S2)  the webview zooms in when tapping a text field. To prevent that and get consistent behavior across the range of android versions I have to set the intialScale to “0”, so the final setting is initialscale 0 and ZoomDensity MEDIUM.

 

 

Logo

All content © 2012-2017.