Track skipping using Volume Up-Down Buttons

From WebOS Internals

Jump to: navigation, search

Contents

Preamble

You will need write permissions to the filesystem on your pre to apply this patch.

To get write persmissions execute:

rootfs_open -w

To remount the filesystem as read-only:

mount -o remount,ro /

Motivation

A headset with in-lined track skip buttons is nice, but when you're using headphones not created for a phone, or you're in a car with the phone connected to the vehicle's sound system, it's nice to be able to skip to the next or previous track with a simple button press. The Blackberry Curve (user FXDemolisher's old phone) had this ability by holding down the volume up/down buttons for a second or so. The track would skip and the volume would be restored to normal. This patch aims to add the same functionality to the Pre.

Procedure

This is one long patch that adds several sections to the following file:

/usr/palm/applications/com.palm.app.musicplayer/app/controllers/localnowplaying-assistant.js
  • Please back up this file before trying this patch.
--- localnowplaying-assistant.js_2009-06-28	Sun Jun 28 17:02:12 2009
+++ localnowplaying-assistant.js	Sun Jun 28 19:28:29 2009 +++
@@ -21,6 +21,15 @@
 			if (title)
 				this.title = title;
 
+			// volume key track skip tracking vars
+			this.currentVolume = -1;
+			this.currentVolumeScenario = undefined;
+			this.currentVolumeSubscription = undefined;
+			this.volumeKeySubscription = undefined;
+			this.volumeKeyDownVolume = -1;
+			this.volumeKeyDownScenario = undefined;
+			this.volumeKeySkipTo = 'none';
+			this.volumeKeySkipTimer = undefined;
 		},
 
 		setup: function() {
@@ -114,14 +123,101 @@
 			AppAssistant.addActiveScene (this);
 			this.refreshAppMenu();
 
+			// subscribe to volume key events
+			this.volumeKeySubscription = new Mojo.Service.Request(
+				'palm://com.palm.keys/audio', 
+				{
+					method: 'status',
+					parameters: {'subscribe': true},
+					onFailure: function() { Mojo.Log.error("Could not subscribe to volume key events"); },
+					onSuccess: this.handleVolumeKeys.bind(this), 
+				});
+				
+			// subscribe to audio events - know what current volume is
+			this.currentVolumeSubscription = new Mojo.Service.Request(
+				'palm://com.palm.audio/media',
+				{
+					method: 'status',
+					parameters: {'subscribe': true},
+					onFailure: function() { Mojo.Log.error("could not subscribe to volume change events"); },
+					onSuccess: this.handleMediaNotifications.bind(this),
+				});
+			
 		},
 
 		stopDragHandler: function(dragEvent) {
 			dragEvent.stop();
 		},
 
-
-
+    		// handlers for the volume keys, used to skip tracks
+		handleVolumeKeys: function(payload) {
+			// down state, record direction if needed and set timer to skip the track
+			if(payload.state === 'down') {
+				// ignore any presses if we are already tracking a down state                         
+			        if(this.volumeKeySkipTo != 'none') { return; } 
+				
+				if(payload.key === 'volume_up') { this.volumeKeySkipTo = 'next'; }
+				else if (payload.key === 'volume_down') { this.volumeKeySkipTo = 'prev'; }
+				
+				// track the volume when the user pressed the key so that we can return to it
+				this.volumeKeyDownVolume = this.currentVolume;
+				this.volumeKeyDownScenario = this.currentScenario;
+				
+				// schedule the check to skip the track 
+				this.volumeKeySkipTimer = this.controller.window.setTimeout(this.checkVolumeKeySkip.bind(this), 1200);
+			} 
+			
+			// up state, clear tracking variables
+			else {
+				this.volumeKeySkipTo = 'none';
+				this.volumeOnLastVolumeKeyPress = -1;
+				this.volumeKeyDownVolume = -1;
+				this.volumeKeyDownScenario = undefined;
+				
+				if(this.volumeKeySkipTimer) {
+					this.controller.window.clearTimeout(this.volumeKeySkipTimer);
+					this.volumeKeySkipTimer = undefined;
+				}
+			}
+		},
+		
+		// check if we need to skip to next/prev track due to user holding down volume keys
+		checkVolumeKeySkip: function() {
+			if(this.volumeKeySkipTo === 'next') { this.nextSong(); }
+			else if (this.volumeKeySkipTo === 'prev') { this.prevSong(true); }
+			
+			this.volumeKeySkipTo = 'none';
+			
+			// set up volume restore in after some time, 
+			// so that user has time to let go of the button
+			setTimeout("assistant.restoreVolume('" + this.volumeKeyDownScenario + "', " + this.volumeKeyDownVolume +")", 1000);
+		},
+		
+		// restore volume to the given scenario and level
+		restoreVolume: function (scenario, volume) {
+			
+			// restore volume to the one when the user held down the button
+			if(volume >= 0) {
+				new Mojo.Service.Request('palm://com.palm.audio/media',
+					{
+						method: 'setVolume',
+						parameters: {scenario: scenario, volume: volume},
+						onFailure: function() { Mojo.Log.error("Could not restore volume"); }
+					});
+			}
+		},
+		
+		// record current volume each time it changes
+		handleMediaNotifications: function(payload) {
+			if(!payload) { return; }
+			
+			if(payload.action == 'changed' && payload.changed.length == 1 && (payload.changed.indexOf('volume') != -1)) {
+				this.currentVolume = payload.volume;
+				this.currentScenario = payload.scenario;
+			}	
+		},
+		
+		
 		handleFlick: function(event){
 			event.stop();
 
@@ -760,7 +856,15 @@
 
 			AppAssistant.removeActiveScene(this);
 
-
+			// clean up subscription to volume keys
+			if(this.volumeKeySubscription) {
+				this.volumeKeySubscription.cancel();
+			}
+			
+			// clean up subscription to volume level
+			if(this.currentVolumeSubscription) {
+				this.currentVolumeSubscription.cancel();
+			}
 		},
 
 		deactivate: function(){

Notes

Track skip at start up issue

The first volume up/down press in the app causes the track to skip. I am not sure why, it may have to do with the amount of time it takes for the bar assistant to come up and does not give enough time for the volume key up event to propagate. ~ FXDemolisher

Timeouts

There are two main timeout values in the code. One controls the amount of time the button has to be held down to skip:

this.volumeKeySkipTimer = this.controller.window.setTimeout(this.checkVolumeKeySkip.bind(this), 1200);

The other controls the amount of time the user has to lift the finger off the key before the volume is restored to normal:

setTimeout("assistant.restoreVolume('" + this.volumeKeyDownScenario + "', " + this.volumeKeyDownVolume +")", 1000);

I kept the skip timeout at 1200 so that the volume doesn't have enough time to go too high when you are wearing headphones but is not short enough to cause random skips. The restore timeout is at 1000 and seems to work fine. If you shorten the second timeout the volume may not restore right since the user is still pressing the button when the setVolume call is made.

~ FXDemolisher
Personal tools
Google AdSense