From 031981ba1bd508e25b80429bece016cb04ebe9d3 Mon Sep 17 00:00:00 2001 From: "Randy Lin ext:(%20and%20Chris%20Jones%20%3Cjones.chris.g%40gmail.com%3E)" Date: Tue, 11 Dec 2012 01:13:08 -0800 Subject: [PATCH] Bug 815452: Hook up FM radio to the audio manager. r=sicking a=blocking-basecamp --- dom/fm/DOMFMRadioChild.js | 34 ++++++++++++++++++++++ dom/fm/DOMFMRadioParent.jsm | 6 +++- dom/fm/FMRadio.cpp | 56 ++++++++++++++++++++++++++++++++++++- dom/fm/FMRadio.h | 8 +++++- dom/fm/nsIFMRadio.idl | 7 ++++- 5 files changed, 107 insertions(+), 4 deletions(-) diff --git a/dom/fm/DOMFMRadioChild.js b/dom/fm/DOMFMRadioChild.js index df07e07c726..a8163b92da2 100644 --- a/dom/fm/DOMFMRadioChild.js +++ b/dom/fm/DOMFMRadioChild.js @@ -73,6 +73,19 @@ DOMFMRadioChild.prototype = { "DOMFMRadio:powerStateChange", "DOMFMRadio:antennaChange"]; this.initHelper(aWindow, messages); + + let els = Cc["@mozilla.org/eventlistenerservice;1"] + .getService(Ci.nsIEventListenerService); + + els.addSystemEventListener(aWindow, "visibilitychange", + this._updateVisibility.bind(this), + /* useCapture = */ true); + + this._visibility = aWindow.document.visibilityState; + // Unlike the |enabled| getter, this is true if *this* DOM window + // has successfully enabled the FM radio more recently than + // disabling it. + this._haveEnabledRadio = false; }, // Called from DOMRequestIpcHelper @@ -130,6 +143,18 @@ DOMFMRadioChild.prototype = { this.dispatchEvent(e); }, + _updateVisibility: function(evt) { + this._visibility = evt.target.visibilityState; + // Only notify visibility state when we "own" the radio stream. + if (this._haveEnabledRadio) { + this._notifyVisibility(); + } + }, + + _notifyVisibility: function() { + cpmm.sendAsyncMessage("DOMFMRadio:updateVisibility", this._visibility); + }, + receiveMessage: function(aMessage) { let msg = aMessage.json; if (msg.mid && msg.mid != this._id) { @@ -153,6 +178,7 @@ DOMFMRadioChild.prototype = { Services.DOMRequest.fireError(request, "Failed to turn on the FM radio"); break; case "DOMFMRadio:disable:Return:OK": + this._haveEnabledRadio = false; request = this.takeRequest(msg.rid); if (!request) { return; @@ -160,6 +186,10 @@ DOMFMRadioChild.prototype = { Services.DOMRequest.fireSuccess(request, null); break; case "DOMFMRadio:disable:Return:NO": + // If disabling the radio failed, but the hardware is still + // on, this DOM window is still responsible for the continued + // playback. + this._haveEnabledRadio = this.enabled; request = this.takeRequest(msg.rid); if (!request) { return; @@ -291,6 +321,10 @@ DOMFMRadioChild.prototype = { }, enable: function nsIDOMFMRadio_enable(frequency) { + // FMRadio::Enable() needs the most recent visibility state + // synchronously. + this._haveEnabledRadio = true; + this._notifyVisibility(); return this._call("enable", frequency); }, diff --git a/dom/fm/DOMFMRadioParent.jsm b/dom/fm/DOMFMRadioParent.jsm index d77189343d0..a251f2bb46e 100644 --- a/dom/fm/DOMFMRadioParent.jsm +++ b/dom/fm/DOMFMRadioParent.jsm @@ -101,7 +101,8 @@ this.DOMFMRadioParent = { "DOMFMRadio:getPowerState", "DOMFMRadio:getFrequency", "DOMFMRadio:getAntennaState", "DOMFMRadio:seekUp", "DOMFMRadio:seekDown", - "DOMFMRadio:cancelSeek" + "DOMFMRadio:cancelSeek", + "DOMFMRadio:updateVisibility", ]; this._messages.forEach(function(msgName) { ppmm.addMessageListener(msgName, this); @@ -458,6 +459,9 @@ this.DOMFMRadioParent = { self._sendMessage("DOMFMRadio:cancelSeek:Return", true, null, msg); } break; + case "DOMFMRadio:updateVisibility": + FMRadio.updateVisible(msg == 'visible'); + break; } } }; diff --git a/dom/fm/FMRadio.cpp b/dom/fm/FMRadio.cpp index 1b5670b1ba6..01673322340 100644 --- a/dom/fm/FMRadio.cpp +++ b/dom/fm/FMRadio.cpp @@ -38,6 +38,7 @@ using mozilla::Preferences; FMRadio::FMRadio() : mHeadphoneState(SWITCH_STATE_OFF) , mHasInternalAntenna(false) + , mHidden(true) { LOG("FMRadio is initialized."); @@ -114,6 +115,23 @@ NS_IMETHODIMP FMRadio::Enable(nsIFMRadioSettings *settings) int32_t upperLimit, lowerLimit, channelWidth; + if (!mAudioChannelAgent) { + nsresult rv; + mAudioChannelAgent = do_CreateInstance("@mozilla.org/audiochannelagent;1", &rv); + if (!mAudioChannelAgent) { + return NS_ERROR_FAILURE; + } + mAudioChannelAgent->Init(AUDIO_CHANNEL_CONTENT, this); + } + + bool canPlay; + mAudioChannelAgent->SetVisibilityState(!mHidden); + mAudioChannelAgent->StartPlaying(&canPlay); + // We enable the hardware, but mute the audio stream, in order to + // simplify state handling. This is simpler but worse for battery + // life; followup is bug 820282. + CanPlayChanged(canPlay); + settings->GetUpperLimit(&upperLimit); settings->GetLowerLimit(&lowerLimit); settings->GetChannelWidth(&channelWidth); @@ -140,12 +158,17 @@ NS_IMETHODIMP FMRadio::Disable() // DisableFMRadio should be called before SetFmRadioAudioEnabled to prevent // the annoying beep sound. DisableFMRadio(); - + nsCOMPtr audioManager = do_GetService(NS_AUDIOMANAGER_CONTRACTID); NS_ENSURE_TRUE(audioManager, NS_OK); audioManager->SetFmRadioAudioEnabled(false); + + if (mAudioChannelAgent) { + mAudioChannelAgent->StopPlaying(); + mAudioChannelAgent = nullptr; + } return NS_OK; } @@ -189,6 +212,15 @@ NS_IMETHODIMP FMRadio::SetFrequency(int32_t frequency) return NS_OK; } +NS_IMETHODIMP FMRadio::UpdateVisible(bool aVisible) +{ + mHidden = !aVisible; + if (mAudioChannelAgent) { + mAudioChannelAgent->SetVisibilityState(!mHidden); + } + return NS_OK; +} + void FMRadio::Notify(const SwitchEvent& aEvent) { if (mHeadphoneState != aEvent.status()) { @@ -211,5 +243,27 @@ void FMRadio::Notify(const FMRadioOperationInformation& info) case FM_RADIO_OPERATION_SEEK: DispatchTrustedEvent(RADIO_SEEK_COMPLETE_EVENT_NAME); break; + default: + MOZ_NOT_REACHED(); + return; } } + +/* void canPlayChanged (in boolean canPlay); */ +NS_IMETHODIMP FMRadio::CanPlayChanged(bool canPlay) +{ + nsCOMPtr audioManager = + do_GetService(NS_AUDIOMANAGER_CONTRACTID); + NS_ENSURE_TRUE(audioManager, NS_OK); + /* mute fm first, it should be better to stop&resume fm */ + if (canPlay) { + int32_t volIdx = 0; + // Restore fm volume, that value is sync as music type + audioManager->GetStreamVolumeIndex(nsIAudioManager::STREAM_TYPE_MUSIC, &volIdx); + audioManager->SetStreamVolumeIndex(nsIAudioManager::STREAM_TYPE_FM, volIdx); + } else { + audioManager->SetStreamVolumeIndex(nsIAudioManager::STREAM_TYPE_FM, 0); + } + return NS_OK; +} + diff --git a/dom/fm/FMRadio.h b/dom/fm/FMRadio.h index 4b8e79ca6d5..bb2d97a1f80 100644 --- a/dom/fm/FMRadio.h +++ b/dom/fm/FMRadio.h @@ -11,6 +11,7 @@ #include "mozilla/HalTypes.h" #include "nsDOMEventTargetHelper.h" #include "nsIFMRadio.h" +#include "AudioChannelService.h" #define NS_FMRADIO_CONTRACTID "@mozilla.org/fmradio;1" // 9cb91834-78a9-4029-b644-7806173c5e2d @@ -26,10 +27,12 @@ class FMRadio : public nsDOMEventTargetHelper , public nsIFMRadio , public hal::FMRadioObserver , public hal::SwitchObserver + , public nsIAudioChannelAgentCallback { public: NS_DECL_ISUPPORTS NS_DECL_NSIFMRADIO + NS_DECL_NSIAUDIOCHANNELAGENTCALLBACK NS_FORWARD_NSIDOMEVENTTARGET(nsDOMEventTargetHelper::) NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED( @@ -41,8 +44,11 @@ public: private: ~FMRadio(); - bool mHasInternalAntenna; + hal::SwitchState mHeadphoneState; + bool mHasInternalAntenna; + bool mHidden; + nsCOMPtr mAudioChannelAgent; }; } // namespace fm diff --git a/dom/fm/nsIFMRadio.idl b/dom/fm/nsIFMRadio.idl index afa21451b5c..df879e4dc01 100644 --- a/dom/fm/nsIFMRadio.idl +++ b/dom/fm/nsIFMRadio.idl @@ -26,7 +26,7 @@ interface nsIFMRadioSettings : nsISupports * * If the WebFM API is re-written in c++ some day, this interface will be useless. */ -[scriptable, builtinclass, uuid(26288adc-d2c1-4fbc-86b5-ecd8173fbf90)] +[scriptable, builtinclass, uuid(9586bc9c-738e-4bcd-907c-ad340a6adc8b)] interface nsIFMRadio : nsIDOMEventTarget { const long SEEK_DIRECTION_UP = 0; const long SEEK_DIRECTION_DOWN = 1; @@ -79,6 +79,11 @@ interface nsIFMRadio : nsIDOMEventTarget { */ void setFrequency(in long frequency); + /** + * Update the visibility state of our client. + */ + void updateVisible(in boolean visible); + /** * Fired when the antenna state is changed. */