Update talk to 60094938.

Review URL: https://webrtc-codereview.appspot.com/7489005

git-svn-id: http://webrtc.googlecode.com/svn/trunk@5420 4adac7df-926f-26a2-2b94-8c16560cd09d
diff --git a/talk/app/webrtc/mediastreamsignaling.cc b/talk/app/webrtc/mediastreamsignaling.cc
index 89129ac..610b3f8 100644
--- a/talk/app/webrtc/mediastreamsignaling.cc
+++ b/talk/app/webrtc/mediastreamsignaling.cc
@@ -295,24 +295,24 @@
   AudioTrackVector audio_tracks = local_stream->GetAudioTracks();
   for (AudioTrackVector::const_iterator it = audio_tracks.begin();
        it != audio_tracks.end(); ++it) {
-    TrackInfos::const_iterator track_info_it =
-        local_audio_tracks_.find((*it)->id());
-    if (track_info_it != local_audio_tracks_.end()) {
-      const TrackInfo& info = track_info_it->second;
-      OnLocalTrackSeen(info.stream_label, info.track_id, info.ssrc,
-                       cricket::MEDIA_TYPE_AUDIO);
+    const TrackInfo* track_info = FindTrackInfo(local_audio_tracks_,
+                                                local_stream->label(),
+                                                (*it)->id());
+    if (track_info) {
+      OnLocalTrackSeen(track_info->stream_label, track_info->track_id,
+                       track_info->ssrc, cricket::MEDIA_TYPE_AUDIO);
     }
   }
 
   VideoTrackVector video_tracks = local_stream->GetVideoTracks();
   for (VideoTrackVector::const_iterator it = video_tracks.begin();
        it != video_tracks.end(); ++it) {
-    TrackInfos::const_iterator track_info_it =
-        local_video_tracks_.find((*it)->id());
-    if (track_info_it != local_video_tracks_.end()) {
-      const TrackInfo& info = track_info_it->second;
-      OnLocalTrackSeen(info.stream_label, info.track_id, info.ssrc,
-                       cricket::MEDIA_TYPE_VIDEO);
+    const TrackInfo* track_info = FindTrackInfo(local_video_tracks_,
+                                                local_stream->label(),
+                                                (*it)->id());
+    if (track_info) {
+      OnLocalTrackSeen(track_info->stream_label, track_info->track_id,
+                       track_info->ssrc, cricket::MEDIA_TYPE_VIDEO);
     }
   }
   return true;
@@ -321,6 +321,7 @@
 void MediaStreamSignaling::RemoveLocalStream(
     MediaStreamInterface* local_stream) {
   local_streams_->RemoveStream(local_stream);
+
   stream_observer_->OnRemoveLocalStream(local_stream);
 }
 
@@ -474,28 +475,6 @@
   }
 }
 
-bool MediaStreamSignaling::GetRemoteAudioTrackSsrc(
-    const std::string& track_id, uint32* ssrc) const {
-  TrackInfos::const_iterator it = remote_audio_tracks_.find(track_id);
-  if (it == remote_audio_tracks_.end()) {
-    return false;
-  }
-
-  *ssrc = it->second.ssrc;
-  return true;
-}
-
-bool MediaStreamSignaling::GetRemoteVideoTrackSsrc(
-    const std::string& track_id, uint32* ssrc) const {
-  TrackInfos::const_iterator it = remote_video_tracks_.find(track_id);
-  if (it == remote_video_tracks_.end()) {
-    return false;
-  }
-
-  *ssrc = it->second.ssrc;
-  return true;
-}
-
 void MediaStreamSignaling::UpdateSessionOptions() {
   options_.streams.clear();
   if (local_streams_ != NULL) {
@@ -554,12 +533,12 @@
   // new StreamParam.
   TrackInfos::iterator track_it = current_tracks->begin();
   while (track_it != current_tracks->end()) {
-    TrackInfo info = track_it->second;
+    const TrackInfo& info = *track_it;
     cricket::StreamParams params;
     if (!cricket::GetStreamBySsrc(streams, info.ssrc, &params) ||
         params.id != info.track_id) {
       OnRemoteTrackRemoved(info.stream_label, info.track_id, media_type);
-      current_tracks->erase(track_it++);
+      track_it = current_tracks->erase(track_it);
     } else {
       ++track_it;
     }
@@ -583,10 +562,10 @@
       new_streams->AddStream(stream);
     }
 
-    TrackInfos::iterator track_it = current_tracks->find(track_id);
-    if (track_it == current_tracks->end()) {
-      (*current_tracks)[track_id] =
-          TrackInfo(stream_label, track_id, ssrc);
+    const TrackInfo* track_info = FindTrackInfo(*current_tracks, stream_label,
+                                                track_id);
+    if (!track_info) {
+      current_tracks->push_back(TrackInfo(stream_label, track_id, ssrc));
       OnRemoteTrackSeen(stream_label, track_id, it->first_ssrc(), media_type);
     }
   }
@@ -642,7 +621,7 @@
   TrackInfos* current_tracks = GetRemoteTracks(media_type);
   for (TrackInfos::iterator track_it = current_tracks->begin();
        track_it != current_tracks->end(); ++track_it) {
-    TrackInfo info = track_it->second;
+    const TrackInfo& info = *track_it;
     MediaStreamInterface* stream = remote_streams_->find(info.stream_label);
     if (media_type == cricket::MEDIA_TYPE_AUDIO) {
       AudioTrackInterface* track = stream->FindAudioTrack(info.track_id);
@@ -695,15 +674,16 @@
   }
   if (remote_info_.default_audio_track_needed &&
       default_remote_stream->GetAudioTracks().size() == 0) {
-    remote_audio_tracks_[kDefaultAudioTrackLabel] =
-        TrackInfo(kDefaultStreamLabel, kDefaultAudioTrackLabel, 0);
+    remote_audio_tracks_.push_back(TrackInfo(kDefaultStreamLabel,
+                                             kDefaultAudioTrackLabel, 0));
+
     OnRemoteTrackSeen(kDefaultStreamLabel, kDefaultAudioTrackLabel, 0,
                        cricket::MEDIA_TYPE_AUDIO);
   }
   if (remote_info_.default_video_track_needed &&
       default_remote_stream->GetVideoTracks().size() == 0) {
-    remote_video_tracks_[kDefaultVideoTrackLabel] =
-        TrackInfo(kDefaultStreamLabel, kDefaultVideoTrackLabel, 0);
+    remote_video_tracks_.push_back(TrackInfo(kDefaultStreamLabel,
+                                             kDefaultVideoTrackLabel, 0));
     OnRemoteTrackSeen(kDefaultStreamLabel, kDefaultVideoTrackLabel, 0,
                        cricket::MEDIA_TYPE_VIDEO);
   }
@@ -736,16 +716,16 @@
     cricket::MediaType media_type) {
   TrackInfos* current_tracks = GetLocalTracks(media_type);
 
-  // Find removed tracks. Ie tracks where the track id or ssrc don't match the
-  // new StreamParam.
+  // Find removed tracks. Ie tracks where the track id, stream label or ssrc
+  // don't match the new StreamParam.
   TrackInfos::iterator track_it = current_tracks->begin();
   while (track_it != current_tracks->end()) {
-    TrackInfo info = track_it->second;
+    const TrackInfo& info = *track_it;
     cricket::StreamParams params;
     if (!cricket::GetStreamBySsrc(streams, info.ssrc, &params) ||
-        params.id != info.track_id) {
+        params.id != info.track_id || params.sync_label != info.stream_label) {
       OnLocalTrackRemoved(info.stream_label, info.track_id, media_type);
-      current_tracks->erase(track_it++);
+      track_it = current_tracks->erase(track_it);
     } else {
       ++track_it;
     }
@@ -759,10 +739,11 @@
     const std::string& stream_label = it->sync_label;
     const std::string& track_id = it->id;
     uint32 ssrc = it->first_ssrc();
-    TrackInfos::iterator track_it =  current_tracks->find(track_id);
-    if (track_it == current_tracks->end()) {
-      (*current_tracks)[track_id] =
-          TrackInfo(stream_label, track_id, ssrc);
+    const TrackInfo* track_info = FindTrackInfo(*current_tracks,
+                                                stream_label,
+                                                track_id);
+    if (!track_info) {
+      current_tracks->push_back(TrackInfo(stream_label, track_id, ssrc));
       OnLocalTrackSeen(stream_label, track_id, it->first_ssrc(),
                        media_type);
     }
@@ -948,4 +929,18 @@
   }
 }
 
+const MediaStreamSignaling::TrackInfo*
+MediaStreamSignaling::FindTrackInfo(
+    const MediaStreamSignaling::TrackInfos& infos,
+    const std::string& stream_label,
+    const std::string track_id) const {
+
+  for (TrackInfos::const_iterator it = infos.begin();
+      it != infos.end(); ++it) {
+    if (it->stream_label == stream_label && it->track_id == track_id)
+      return &*it;
+  }
+  return NULL;
+}
+
 }  // namespace webrtc
diff --git a/talk/app/webrtc/mediastreamsignaling.h b/talk/app/webrtc/mediastreamsignaling.h
index 6c54f40..c730b46 100644
--- a/talk/app/webrtc/mediastreamsignaling.h
+++ b/talk/app/webrtc/mediastreamsignaling.h
@@ -238,10 +238,6 @@
   // Called when the data channel closes.
   void OnDataChannelClose();
 
-  // Returns the SSRC for a given track.
-  bool GetRemoteAudioTrackSsrc(const std::string& track_id, uint32* ssrc) const;
-  bool GetRemoteVideoTrackSsrc(const std::string& track_id, uint32* ssrc) const;
-
   // Returns all current known local MediaStreams.
   StreamCollectionInterface* local_streams() const { return local_streams_;}
 
@@ -287,7 +283,7 @@
     std::string track_id;
     uint32 ssrc;
   };
-  typedef std::map<std::string, TrackInfo> TrackInfos;
+  typedef std::vector<TrackInfo> TrackInfos;
 
   void UpdateSessionOptions();
 
@@ -366,6 +362,10 @@
       const std::vector<std::string>& active_channels, bool is_local_update);
   void CreateRemoteDataChannel(const std::string& label, uint32 remote_ssrc);
 
+  const TrackInfo* FindTrackInfo(const TrackInfos& infos,
+                                 const std::string& stream_label,
+                                 const std::string track_id) const;
+
   RemotePeerInfo remote_info_;
   talk_base::Thread* signaling_thread_;
   DataChannelFactory* data_channel_factory_;
diff --git a/talk/app/webrtc/mediastreamsignaling_unittest.cc b/talk/app/webrtc/mediastreamsignaling_unittest.cc
index 6debcfd..49625ef 100644
--- a/talk/app/webrtc/mediastreamsignaling_unittest.cc
+++ b/talk/app/webrtc/mediastreamsignaling_unittest.cc
@@ -26,6 +26,7 @@
  */
 
 #include <string>
+#include <vector>
 
 #include "talk/app/webrtc/audiotrack.h"
 #include "talk/app/webrtc/mediastream.h"
@@ -383,31 +384,48 @@
     std::string track_id;
     uint32 ssrc;
   };
-  typedef std::map<std::string, TrackInfo> TrackInfos;
+  typedef std::vector<TrackInfo> TrackInfos;
 
   void AddTrack(TrackInfos* track_infos, MediaStreamInterface* stream,
                 MediaStreamTrackInterface* track,
                 uint32 ssrc) {
-    (*track_infos)[track->id()] = TrackInfo(stream->label(), track->id(),
-                                            ssrc);
+    (*track_infos).push_back(TrackInfo(stream->label(), track->id(),
+                                       ssrc));
   }
 
   void RemoveTrack(TrackInfos* track_infos, MediaStreamInterface* stream,
                    MediaStreamTrackInterface* track) {
-    TrackInfos::iterator it = track_infos->find(track->id());
-    ASSERT_TRUE(it != track_infos->end());
-    ASSERT_EQ(it->second.stream_label, stream->label());
-    track_infos->erase(it);
+    for (TrackInfos::iterator it = track_infos->begin();
+         it != track_infos->end(); ++it) {
+      if (it->stream_label == stream->label() && it->track_id == track->id()) {
+        track_infos->erase(it);
+        return;
+      }
+    }
+    ADD_FAILURE();
   }
 
+  const TrackInfo* FindTrackInfo(const TrackInfos& infos,
+                                 const std::string& stream_label,
+                                 const std::string track_id) const {
+    for (TrackInfos::const_iterator it = infos.begin();
+        it != infos.end(); ++it) {
+      if (it->stream_label == stream_label && it->track_id == track_id)
+        return &*it;
+    }
+    return NULL;
+  }
+
+
   void VerifyTrack(const TrackInfos& track_infos,
                    const std::string& stream_label,
                    const std::string& track_id,
                    uint32 ssrc) {
-    TrackInfos::const_iterator it = track_infos.find(track_id);
-    ASSERT_TRUE(it != track_infos.end());
-    EXPECT_EQ(stream_label, it->second.stream_label);
-    EXPECT_EQ(ssrc, it->second.ssrc);
+    const TrackInfo* track_info = FindTrackInfo(track_infos,
+                                                stream_label,
+                                                track_id);
+    ASSERT_TRUE(track_info != NULL);
+    EXPECT_EQ(ssrc, track_info->ssrc);
   }
 
   TrackInfos remote_audio_tracks_;
@@ -1051,6 +1069,47 @@
   observer_->VerifyLocalVideoTrack(kStreams[0], kVideoTracks[0], 98);
 }
 
+// This test that the correct MediaStreamSignalingObserver methods are called
+// if a new session description is set with the same tracks but they are now
+// sent on a another MediaStream.
+TEST_F(MediaStreamSignalingTest, SignalSameTracksInSeparateMediaStream) {
+  talk_base::scoped_ptr<SessionDescriptionInterface> desc;
+  CreateSessionDescriptionAndReference(1, 1, desc.use());
+
+  signaling_->AddLocalStream(reference_collection_->at(0));
+  signaling_->OnLocalDescriptionChanged(desc.get());
+  EXPECT_EQ(1u, observer_->NumberOfLocalAudioTracks());
+  EXPECT_EQ(1u, observer_->NumberOfLocalVideoTracks());
+
+  std::string stream_label_0 = kStreams[0];
+  observer_->VerifyLocalAudioTrack(stream_label_0, kAudioTracks[0], 1);
+  observer_->VerifyLocalVideoTrack(stream_label_0, kVideoTracks[0], 2);
+
+  // Add a new MediaStream but with the same tracks as in the first stream.
+  std::string stream_label_1 = kStreams[1];
+  talk_base::scoped_refptr<webrtc::MediaStreamInterface> stream_1(
+      webrtc::MediaStream::Create(kStreams[1]));
+  stream_1->AddTrack(reference_collection_->at(0)->GetVideoTracks()[0]);
+  stream_1->AddTrack(reference_collection_->at(0)->GetAudioTracks()[0]);
+  signaling_->AddLocalStream(stream_1);
+
+  // Replace msid in the original SDP.
+  std::string sdp;
+  desc->ToString(&sdp);
+  talk_base::replace_substrs(
+      kStreams[0], strlen(kStreams[0]), kStreams[1], strlen(kStreams[1]), &sdp);
+
+  talk_base::scoped_ptr<SessionDescriptionInterface> updated_desc(
+      webrtc::CreateSessionDescription(SessionDescriptionInterface::kOffer,
+                                       sdp, NULL));
+
+  signaling_->OnLocalDescriptionChanged(updated_desc.get());
+  observer_->VerifyLocalAudioTrack(kStreams[1], kAudioTracks[0], 1);
+  observer_->VerifyLocalVideoTrack(kStreams[1], kVideoTracks[0], 2);
+  EXPECT_EQ(1u, observer_->NumberOfLocalAudioTracks());
+  EXPECT_EQ(1u, observer_->NumberOfLocalVideoTracks());
+}
+
 // Verifies that an even SCTP id is allocated for SSL_CLIENT and an odd id for
 // SSL_SERVER.
 TEST_F(MediaStreamSignalingTest, SctpIdAllocationBasedOnRole) {
diff --git a/talk/app/webrtc/peerconnectionfactory.cc b/talk/app/webrtc/peerconnectionfactory.cc
index ee15b5d..dc14bfb 100644
--- a/talk/app/webrtc/peerconnectionfactory.cc
+++ b/talk/app/webrtc/peerconnectionfactory.cc
@@ -106,10 +106,10 @@
 };
 
 struct StartAecDumpParams : public talk_base::MessageData {
-  explicit StartAecDumpParams(FILE* aec_dump_file)
+  explicit StartAecDumpParams(talk_base::PlatformFile aec_dump_file)
       : aec_dump_file(aec_dump_file) {
   }
-  FILE* aec_dump_file;
+  talk_base::PlatformFile aec_dump_file;
   bool result;
 };
 
@@ -289,7 +289,7 @@
   return VideoSourceProxy::Create(signaling_thread_, source);
 }
 
-bool PeerConnectionFactory::StartAecDump_s(FILE* file) {
+bool PeerConnectionFactory::StartAecDump_s(talk_base::PlatformFile file) {
   return channel_manager_->StartAecDump(file);
 }
 
@@ -380,7 +380,7 @@
   return AudioTrackProxy::Create(signaling_thread_, track);
 }
 
-bool PeerConnectionFactory::StartAecDump(FILE* file) {
+bool PeerConnectionFactory::StartAecDump(talk_base::PlatformFile file) {
   StartAecDumpParams params(file);
   signaling_thread_->Send(this, MSG_START_AEC_DUMP, &params);
   return params.result;
diff --git a/talk/app/webrtc/peerconnectionfactory.h b/talk/app/webrtc/peerconnectionfactory.h
index 63d37f0..46b095f 100644
--- a/talk/app/webrtc/peerconnectionfactory.h
+++ b/talk/app/webrtc/peerconnectionfactory.h
@@ -78,7 +78,7 @@
       CreateAudioTrack(const std::string& id,
                        AudioSourceInterface* audio_source);
 
-  virtual bool StartAecDump(FILE* file);
+  virtual bool StartAecDump(talk_base::PlatformFile file);
 
   virtual cricket::ChannelManager* channel_manager();
   virtual talk_base::Thread* signaling_thread();
@@ -109,7 +109,7 @@
       PortAllocatorFactoryInterface* allocator_factory,
       DTLSIdentityServiceInterface* dtls_identity_service,
       PeerConnectionObserver* observer);
-  bool StartAecDump_s(FILE* file);
+  bool StartAecDump_s(talk_base::PlatformFile file);
 
   // Implements talk_base::MessageHandler.
   void OnMessage(talk_base::Message* msg);
diff --git a/talk/app/webrtc/peerconnectioninterface.h b/talk/app/webrtc/peerconnectioninterface.h
index d24c9a9..667774e2 100644
--- a/talk/app/webrtc/peerconnectioninterface.h
+++ b/talk/app/webrtc/peerconnectioninterface.h
@@ -76,6 +76,7 @@
 #include "talk/app/webrtc/jsep.h"
 #include "talk/app/webrtc/mediastreaminterface.h"
 #include "talk/app/webrtc/statstypes.h"
+#include "talk/base/fileutils.h"
 #include "talk/base/socketaddress.h"
 
 namespace talk_base {
@@ -442,9 +443,10 @@
 
   // Starts AEC dump using existing file. Takes ownership of |file| and passes
   // it on to VoiceEngine (via other objects) immediately, which will take
-  // the ownerhip.
+  // the ownerhip. If the operation fails, the file will be closed.
   // TODO(grunell): Remove when Chromium has started to use AEC in each source.
-  virtual bool StartAecDump(FILE* file) = 0;
+  // http://crbug.com/264611.
+  virtual bool StartAecDump(talk_base::PlatformFile file) = 0;
 
  protected:
   // Dtor and ctor protected as objects shouldn't be created or deleted via
diff --git a/talk/base/fileutils.cc b/talk/base/fileutils.cc
index ff34147..d73997a 100644
--- a/talk/base/fileutils.cc
+++ b/talk/base/fileutils.cc
@@ -28,6 +28,9 @@
 #include <cassert>
 
 #ifdef WIN32
+// TODO(grunell): Remove io.h includes when Chromium has started
+// to use AEC in each source. http://crbug.com/264611.
+#include <io.h>
 #include "talk/base/win32.h"
 #endif
 
@@ -294,4 +297,28 @@
   return true;
 }
 
+// Taken from Chromium's base/platform_file_*.cc.
+// TODO(grunell): Remove when Chromium has started to use AEC in each source.
+// http://crbug.com/264611.
+FILE* FdopenPlatformFileForWriting(PlatformFile file) {
+#if defined(WIN32)
+  if (file == kInvalidPlatformFileValue)
+    return NULL;
+  int fd = _open_osfhandle(reinterpret_cast<intptr_t>(file), 0);
+  if (fd < 0)
+    return NULL;
+  return _fdopen(fd, "w");
+#else
+  return fdopen(file, "w");
+#endif
+}
+
+bool ClosePlatformFile(PlatformFile file) {
+#if defined(WIN32)
+  return CloseHandle(file) != 0;
+#else
+  return close(file);
+#endif
+}
+
 }  // namespace talk_base
diff --git a/talk/base/fileutils.h b/talk/base/fileutils.h
index 186c963..fba0d00 100644
--- a/talk/base/fileutils.h
+++ b/talk/base/fileutils.h
@@ -452,6 +452,21 @@
 // process).
 bool CreateUniqueFile(Pathname& path, bool create_empty);
 
+// Taken from Chromium's base/platform_file.h.
+// Don't use ClosePlatformFile to close a file opened with FdopenPlatformFile.
+// Use fclose instead.
+// TODO(grunell): Remove when Chromium has started to use AEC in each source.
+// http://crbug.com/264611.
+#if defined(WIN32)
+typedef HANDLE PlatformFile;
+const PlatformFile kInvalidPlatformFileValue = INVALID_HANDLE_VALUE;
+#elif defined(POSIX)
+typedef int PlatformFile;
+const PlatformFile kInvalidPlatformFileValue = -1;
+#endif
+FILE* FdopenPlatformFileForWriting(PlatformFile file);
+bool ClosePlatformFile(PlatformFile file);
+
 }  // namespace talk_base
 
 #endif  // TALK_BASE_FILEUTILS_H_
diff --git a/talk/libjingle.gyp b/talk/libjingle.gyp
index e77e48a..38a165b 100755
--- a/talk/libjingle.gyp
+++ b/talk/libjingle.gyp
@@ -509,6 +509,8 @@
         'xmpp/pubsub_task.h',
         'xmpp/pubsubclient.cc',
         'xmpp/pubsubclient.h',
+        'xmpp/pubsubstateclient.cc',
+        'xmpp/pubsubstateclient.h',
         'xmpp/pubsubtasks.cc',
         'xmpp/pubsubtasks.h',
         'xmpp/receivetask.cc',
diff --git a/talk/libjingle.scons b/talk/libjingle.scons
index bab18b2..d879cc2 100644
--- a/talk/libjingle.scons
+++ b/talk/libjingle.scons
@@ -486,7 +486,7 @@
            "jingle",
          ],
          srcs = [
-           "p2p/base/relayserver_main.cc",
+           "examples/relayserver/relayserver_main.cc",
          ],
 )
 talk.App(env, name = "stunserver",
@@ -494,7 +494,7 @@
            "jingle",
          ],
          srcs = [
-           "p2p/base/stunserver_main.cc",
+           "examples/stunserver/stunserver_main.cc",
          ],
 )
 talk.App(env, name = "turnserver",
@@ -503,7 +503,7 @@
            "ssl",
          ],
          srcs = [
-           "p2p/base/turnserver_main.cc",
+           "examples/turnserver/turnserver_main.cc",
          ],
          libs = [
            "jingle",
diff --git a/talk/media/base/fakemediaengine.h b/talk/media/base/fakemediaengine.h
index 9ffe074..28facca 100644
--- a/talk/media/base/fakemediaengine.h
+++ b/talk/media/base/fakemediaengine.h
@@ -790,7 +790,7 @@
 
   bool SetLocalMonitor(bool enable) { return true; }
 
-  bool StartAecDump(FILE* file) { return false; }
+  bool StartAecDump(talk_base::PlatformFile file) { return false; }
 
   bool RegisterProcessor(uint32 ssrc, VoiceProcessor* voice_processor,
                          MediaProcessorDirection direction) {
diff --git a/talk/media/base/filemediaengine.h b/talk/media/base/filemediaengine.h
index e7956ec..be196ae 100644
--- a/talk/media/base/filemediaengine.h
+++ b/talk/media/base/filemediaengine.h
@@ -133,7 +133,7 @@
   virtual bool FindVideoCodec(const VideoCodec& codec) { return true; }
   virtual void SetVoiceLogging(int min_sev, const char* filter) {}
   virtual void SetVideoLogging(int min_sev, const char* filter) {}
-  virtual bool StartAecDump(FILE* file) { return false; }
+  virtual bool StartAecDump(talk_base::PlatformFile) { return false; }
 
   virtual bool RegisterVideoProcessor(VideoProcessor* processor) {
     return true;
diff --git a/talk/media/base/mediaengine.h b/talk/media/base/mediaengine.h
index 6e071ec..93586bb 100644
--- a/talk/media/base/mediaengine.h
+++ b/talk/media/base/mediaengine.h
@@ -36,6 +36,7 @@
 #include <string>
 #include <vector>
 
+#include "talk/base/fileutils.h"
 #include "talk/base/sigslotrepeater.h"
 #include "talk/media/base/codec.h"
 #include "talk/media/base/mediachannel.h"
@@ -136,7 +137,7 @@
   virtual void SetVideoLogging(int min_sev, const char* filter) = 0;
 
   // Starts AEC dump using existing file.
-  virtual bool StartAecDump(FILE* file) = 0;
+  virtual bool StartAecDump(talk_base::PlatformFile file) = 0;
 
   // Voice processors for effects.
   virtual bool RegisterVoiceProcessor(uint32 ssrc,
@@ -256,7 +257,7 @@
     video_.SetLogging(min_sev, filter);
   }
 
-  virtual bool StartAecDump(FILE* file) {
+  virtual bool StartAecDump(talk_base::PlatformFile file) {
     return voice_.StartAecDump(file);
   }
 
@@ -316,7 +317,7 @@
     return rtp_header_extensions_;
   }
   void SetLogging(int min_sev, const char* filter) {}
-  bool StartAecDump(FILE* file) { return false; }
+  bool StartAecDump(talk_base::PlatformFile file) { return false; }
   bool RegisterProcessor(uint32 ssrc,
                          VoiceProcessor* voice_processor,
                          MediaProcessorDirection direction) { return true; }
diff --git a/talk/media/webrtc/fakewebrtcvideoengine.h b/talk/media/webrtc/fakewebrtcvideoengine.h
index bb75c2a..0b07925 100644
--- a/talk/media/webrtc/fakewebrtcvideoengine.h
+++ b/talk/media/webrtc/fakewebrtcvideoengine.h
@@ -640,9 +640,7 @@
   }
   WEBRTC_STUB(RegisterCpuOveruseObserver,
       (int channel, webrtc::CpuOveruseObserver* observer));
-#ifdef USE_WEBRTC_DEV_BRANCH
   WEBRTC_STUB(CpuOveruseMeasures, (int, int*, int*, int*, int*));
-#endif
   WEBRTC_STUB(ConnectAudioChannel, (const int, const int));
   WEBRTC_STUB(DisconnectAudioChannel, (const int));
   WEBRTC_FUNC(StartSend, (const int channel)) {
@@ -827,12 +825,8 @@
   }
   WEBRTC_STUB(RegisterSendTransport, (const int, webrtc::Transport&));
   WEBRTC_STUB(DeregisterSendTransport, (const int));
-#ifdef USE_WEBRTC_DEV_BRANCH
   WEBRTC_STUB(ReceivedRTPPacket, (const int, const void*, const int,
       const webrtc::PacketTime&));
-#else
-  WEBRTC_STUB(ReceivedRTPPacket, (const int, const void*, const int));
-#endif
   WEBRTC_STUB(ReceivedRTCPPacket, (const int, const void*, const int));
   // Not using WEBRTC_STUB due to bool return value
   virtual bool IsIPv6Enabled(int channel) { return true; }
@@ -1040,9 +1034,7 @@
     channels_[channel]->rtp_absolute_send_time_receive_id_ = (enable) ? id : 0;
     return 0;
   }
-#ifdef USE_WEBRTC_DEV_BRANCH
   WEBRTC_STUB(SetRtcpXrRrtrStatus, (int, bool));
-#endif
   WEBRTC_FUNC(SetTransmissionSmoothingStatus, (int channel, bool enable)) {
     WEBRTC_CHECK_CHANNEL(channel);
     channels_[channel]->transmission_smoothing_ = enable;
diff --git a/talk/media/webrtc/fakewebrtcvoiceengine.h b/talk/media/webrtc/fakewebrtcvoiceengine.h
index a68d65e..0eb880b 100644
--- a/talk/media/webrtc/fakewebrtcvoiceengine.h
+++ b/talk/media/webrtc/fakewebrtcvoiceengine.h
@@ -631,13 +631,11 @@
 
   // webrtc::VoENetEqStats
   WEBRTC_STUB(GetNetworkStatistics, (int, webrtc::NetworkStatistics&));
-#ifdef USE_WEBRTC_DEV_BRANCH
   WEBRTC_FUNC_CONST(GetDecodingCallStatistics, (int channel,
       webrtc::AudioDecodingCallStats*)) {
     WEBRTC_CHECK_CHANNEL(channel);
     return 0;
   }
-#endif
 
   // webrtc::VoENetwork
   WEBRTC_FUNC(RegisterExternalTransport, (int channel,
@@ -923,9 +921,7 @@
   WEBRTC_STUB(GetEcDelayMetrics, (int& delay_median, int& delay_std));
 
   WEBRTC_STUB(StartDebugRecording, (const char* fileNameUTF8));
-#ifdef USE_WEBRTC_DEV_BRANCH
   WEBRTC_STUB(StartDebugRecording, (FILE* handle));
-#endif
   WEBRTC_STUB(StopDebugRecording, ());
 
   WEBRTC_FUNC(SetTypingDetectionStatus, (bool enable)) {
diff --git a/talk/media/webrtc/webrtcmediaengine.h b/talk/media/webrtc/webrtcmediaengine.h
index 82abefa..84319a1 100644
--- a/talk/media/webrtc/webrtcmediaengine.h
+++ b/talk/media/webrtc/webrtcmediaengine.h
@@ -145,7 +145,7 @@
   virtual void SetVideoLogging(int min_sev, const char* filter) OVERRIDE {
     delegate_->SetVideoLogging(min_sev, filter);
   }
-  virtual bool StartAecDump(FILE* file) OVERRIDE {
+  virtual bool StartAecDump(talk_base::PlatformFile file) OVERRIDE {
     return delegate_->StartAecDump(file);
   }
   virtual bool RegisterVoiceProcessor(
diff --git a/talk/media/webrtc/webrtcvideoengine.cc b/talk/media/webrtc/webrtcvideoengine.cc
index 63c9eff..ca0ed41 100644
--- a/talk/media/webrtc/webrtcvideoengine.cc
+++ b/talk/media/webrtc/webrtcvideoengine.cc
@@ -2300,7 +2300,6 @@
       sinfo.encode_usage_percent = -1;
       sinfo.capture_queue_delay_ms_per_s = -1;
 
-#ifdef USE_WEBRTC_DEV_BRANCH
       int capture_jitter_ms = 0;
       int avg_encode_time_ms = 0;
       int encode_usage_percent = 0;
@@ -2316,7 +2315,6 @@
         sinfo.encode_usage_percent = encode_usage_percent;
         sinfo.capture_queue_delay_ms_per_s = capture_queue_delay_ms_per_s;
       }
-#endif
 
       // Get received RTCP statistics for the sender (reported by the remote
       // client in a RTCP packet), if available.
@@ -2465,9 +2463,7 @@
   MaybeDisconnectCapturer(old_capturer);
 
   send_channel->set_video_capturer(capturer);
-  capturer->SignalVideoFrame.connect(
-      this,
-      &WebRtcVideoMediaChannel::SendFrame);
+  MaybeConnectCapturer(capturer);
   if (!capturer->IsScreencast() && ratio_w_ != 0 && ratio_h_ != 0) {
     capturer->UpdateAspectRatio(ratio_w_, ratio_h_);
   }
@@ -2500,12 +2496,8 @@
   engine()->vie()->network()->ReceivedRTPPacket(
       which_channel,
       packet->data(),
-#ifdef USE_WEBRTC_DEV_BRANCH
       static_cast<int>(packet->length()),
       webrtc::PacketTime(packet_time.timestamp, packet_time.not_before));
-#else
-      static_cast<int>(packet->length()));
-#endif
 }
 
 void WebRtcVideoMediaChannel::OnRtcpReceived(
diff --git a/talk/media/webrtc/webrtcvideoengine_unittest.cc b/talk/media/webrtc/webrtcvideoengine_unittest.cc
index 24eae46..e331188 100644
--- a/talk/media/webrtc/webrtcvideoengine_unittest.cc
+++ b/talk/media/webrtc/webrtcvideoengine_unittest.cc
@@ -1274,11 +1274,7 @@
   EXPECT_FALSE(vie_.GetCaptureDenoising(capture_id));
 }
 
-// Test disabled because it drops frames when adapt-before-effects is turned
-// off (turned off because it was exposing a crash - see bug 12250150). This is
-// safe for now because this test exercises an unused feature.
-// TODO(tpsiaki) reenable once adapt-before-effects is turned back on.
-TEST_F(WebRtcVideoEngineTestFake, DISABLED_MultipleSendStreamsWithOneCapturer) {
+TEST_F(WebRtcVideoEngineTestFake, MultipleSendStreamsWithOneCapturer) {
   EXPECT_TRUE(SetupEngine());
 
   // Start the capturer
diff --git a/talk/media/webrtc/webrtcvoiceengine.cc b/talk/media/webrtc/webrtcvoiceengine.cc
index 4d17057..b4b96d1 100644
--- a/talk/media/webrtc/webrtcvoiceengine.cc
+++ b/talk/media/webrtc/webrtcvoiceengine.cc
@@ -1433,20 +1433,23 @@
   return true;
 }
 
-bool WebRtcVoiceEngine::StartAecDump(FILE* file) {
-#ifdef USE_WEBRTC_DEV_BRANCH
+bool WebRtcVoiceEngine::StartAecDump(talk_base::PlatformFile file) {
+  FILE* aec_dump_file_stream = talk_base::FdopenPlatformFileForWriting(file);
+  if (!aec_dump_file_stream) {
+    LOG(LS_ERROR) << "Could not open AEC dump file stream.";
+    if (!talk_base::ClosePlatformFile(file))
+      LOG(LS_WARNING) << "Could not close file.";
+    return false;
+  }
   StopAecDump();
-  if (voe_wrapper_->processing()->StartDebugRecording(file) !=
+  if (voe_wrapper_->processing()->StartDebugRecording(aec_dump_file_stream) !=
       webrtc::AudioProcessing::kNoError) {
-    LOG_RTCERR1(StartDebugRecording, "FILE*");
-    fclose(file);
+    LOG_RTCERR0(StartDebugRecording);
+    fclose(aec_dump_file_stream);
     return false;
   }
   is_dumping_aec_ = true;
   return true;
-#else
-  return false;
-#endif
 }
 
 bool WebRtcVoiceEngine::RegisterProcessor(
diff --git a/talk/media/webrtc/webrtcvoiceengine.h b/talk/media/webrtc/webrtcvoiceengine.h
index e50bb3c..4b31656 100644
--- a/talk/media/webrtc/webrtcvoiceengine.h
+++ b/talk/media/webrtc/webrtcvoiceengine.h
@@ -175,7 +175,7 @@
                             webrtc::AudioDeviceModule* adm_sc);
 
   // Starts AEC dump using existing file.
-  bool StartAecDump(FILE* file);
+  bool StartAecDump(talk_base::PlatformFile file);
 
   // Check whether the supplied trace should be ignored.
   bool ShouldIgnoreTrace(const std::string& trace);
diff --git a/talk/p2p/base/turnport_unittest.cc b/talk/p2p/base/turnport_unittest.cc
index d559894..0284f51 100644
--- a/talk/p2p/base/turnport_unittest.cc
+++ b/talk/p2p/base/turnport_unittest.cc
@@ -24,6 +24,9 @@
  * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
+#if defined(POSIX)
+#include <dirent.h>
+#endif
 
 #include "talk/base/asynctcpsocket.h"
 #include "talk/base/buffer.h"
@@ -71,7 +74,7 @@
 static const char kIcePwd2[] = "TESTICEPWD00000000000002";
 static const char kTurnUsername[] = "test";
 static const char kTurnPassword[] = "test";
-static const int kTimeout = 1000;
+static const unsigned int kTimeout = 1000;
 
 static const cricket::ProtocolAddress kTurnUdpProtoAddr(
     kTurnUdpIntAddr, cricket::PROTO_UDP);
@@ -80,8 +83,26 @@
 static const cricket::ProtocolAddress kTurnUdpIPv6ProtoAddr(
     kTurnUdpIPv6IntAddr, cricket::PROTO_UDP);
 
+static const unsigned int MSG_TESTFINISH = 0;
+
+#if defined(LINUX)
+static int GetFDCount() {
+  struct dirent *dp;
+  int fd_count = 0;
+  DIR *dir = opendir("/proc/self/fd/");
+  while ((dp = readdir(dir)) != NULL) {
+    if (dp->d_name[0] == '.')
+      continue;
+    ++fd_count;
+  }
+  closedir(dir);
+  return fd_count;
+}
+#endif
+
 class TurnPortTest : public testing::Test,
-                     public sigslot::has_slots<> {
+                     public sigslot::has_slots<>,
+                     public talk_base::MessageHandler {
  public:
   TurnPortTest()
       : main_(talk_base::Thread::Current()),
@@ -95,10 +116,17 @@
         turn_error_(false),
         turn_unknown_address_(false),
         turn_create_permission_success_(false),
-        udp_ready_(false) {
+        udp_ready_(false),
+        test_finish_(false) {
     network_.AddIP(talk_base::IPAddress(INADDR_ANY));
   }
 
+  virtual void OnMessage(talk_base::Message* msg) {
+    ASSERT(msg->message_id == MSG_TESTFINISH);
+    if (msg->message_id == MSG_TESTFINISH)
+      test_finish_ = true;
+  }
+
   void OnTurnPortComplete(Port* port) {
     turn_ready_ = true;
   }
@@ -264,6 +292,7 @@
   bool turn_unknown_address_;
   bool turn_create_permission_success_;
   bool udp_ready_;
+  bool test_finish_;
   std::vector<talk_base::Buffer> turn_packets_;
   std::vector<talk_base::Buffer> udp_packets_;
 };
@@ -378,3 +407,24 @@
   EXPECT_NE(0, turn_port_->Candidates()[0].address().port());
 }
 
+// This test verifies any FD's are not leaked after TurnPort is destroyed.
+// https://code.google.com/p/webrtc/issues/detail?id=2651
+#if defined(LINUX)
+TEST_F(TurnPortTest, TestResolverShutdown) {
+  turn_server_.AddInternalSocket(kTurnUdpIPv6IntAddr, cricket::PROTO_UDP);
+  int last_fd_count = GetFDCount();
+  // Need to supply unresolved address to kick off resolver.
+  CreateTurnPort(kLocalIPv6Addr, kTurnUsername, kTurnPassword,
+                 cricket::ProtocolAddress(talk_base::SocketAddress(
+                    "stun.l.google.com", 3478), cricket::PROTO_UDP));
+  turn_port_->PrepareAddress();
+  ASSERT_TRUE_WAIT(turn_error_, kTimeout);
+  EXPECT_TRUE(turn_port_->Candidates().empty());
+  turn_port_.reset();
+  talk_base::Thread::Current()->Post(this, MSG_TESTFINISH);
+  // Waiting for above message to be processed.
+  ASSERT_TRUE_WAIT(test_finish_, kTimeout);
+  EXPECT_EQ(last_fd_count, GetFDCount());
+}
+#endif
+
diff --git a/talk/session/media/channelmanager.cc b/talk/session/media/channelmanager.cc
index 4d5d8fc..ccc3527 100644
--- a/talk/session/media/channelmanager.cc
+++ b/talk/session/media/channelmanager.cc
@@ -947,7 +947,7 @@
   return true;
 }
 
-bool ChannelManager::StartAecDump(FILE* file) {
+bool ChannelManager::StartAecDump(talk_base::PlatformFile file) {
   return worker_thread_->Invoke<bool>(
       Bind(&MediaEngineInterface::StartAecDump, media_engine_.get(), file));
 }
diff --git a/talk/session/media/channelmanager.h b/talk/session/media/channelmanager.h
index f19d3d0..deb7b9e 100644
--- a/talk/session/media/channelmanager.h
+++ b/talk/session/media/channelmanager.h
@@ -32,6 +32,7 @@
 #include <vector>
 
 #include "talk/base/criticalsection.h"
+#include "talk/base/fileutils.h"
 #include "talk/base/sigslotrepeater.h"
 #include "talk/base/thread.h"
 #include "talk/media/base/capturemanager.h"
@@ -215,7 +216,7 @@
                                       const VideoFormat& max_format);
 
   // Starts AEC dump using existing file.
-  bool StartAecDump(FILE* file);
+  bool StartAecDump(talk_base::PlatformFile file);
 
   sigslot::repeater0<> SignalDevicesChange;
   sigslot::signal2<VideoCapturer*, CaptureState> SignalVideoCaptureStateChange;
diff --git a/talk/xmpp/hangoutpubsubclient.cc b/talk/xmpp/hangoutpubsubclient.cc
index b6669a1..8e92a7b 100644
--- a/talk/xmpp/hangoutpubsubclient.cc
+++ b/talk/xmpp/hangoutpubsubclient.cc
@@ -44,54 +44,8 @@
 const char kNotPresenting[] = "o";
 const char kEmpty[] = "";
 
-const std::string GetPublisherNickFromPubSubItem(const XmlElement* item_elem) {
-  if (item_elem == NULL) {
-    return "";
-  }
-
-  return Jid(item_elem->Attr(QN_ATTR_PUBLISHER)).resource();
-}
-
 }  // namespace
 
-
-// Knows how to handle specific states and XML.
-template <typename C>
-class PubSubStateSerializer {
- public:
-  virtual ~PubSubStateSerializer() {}
-  virtual XmlElement* Write(const QName& state_name, const C& state) = 0;
-  virtual C Parse(const XmlElement* state_elem) = 0;
-};
-
-// Knows how to create "keys" for states, which determines their
-// uniqueness.  Most states are per-nick, but block is
-// per-blocker-and-blockee.  This is independent of itemid, especially
-// in the case of presenter state.
-class PubSubStateKeySerializer {
- public:
-  virtual ~PubSubStateKeySerializer() {}
-  virtual std::string GetKey(const std::string& publisher_nick,
-                             const std::string& published_nick) = 0;
-};
-
-class PublishedNickKeySerializer : public PubSubStateKeySerializer {
- public:
-  virtual std::string GetKey(const std::string& publisher_nick,
-                             const std::string& published_nick) {
-    return published_nick;
-  }
-};
-
-class PublisherAndPublishedNicksKeySerializer
-    : public PubSubStateKeySerializer {
- public:
-  virtual std::string GetKey(const std::string& publisher_nick,
-                             const std::string& published_nick) {
-    return publisher_nick + ":" + published_nick;
-  }
-};
-
 // A simple serialiazer where presence of item => true, lack of item
 // => false.
 class BoolStateSerializer : public PubSubStateSerializer<bool> {
@@ -103,195 +57,11 @@
     return new XmlElement(state_name, true);
   }
 
-  virtual bool Parse(const XmlElement* state_elem) {
-    return state_elem != NULL;
+  virtual void Parse(const XmlElement* state_elem, bool *state_out) {
+    *state_out = state_elem != NULL;
   }
 };
 
-// Adapts PubSubClient to be specifically suited for pub sub call
-// states.  Signals state changes and keeps track of keys, which are
-// normally nicks.
-// TODO: Expose this as a generally useful class, not just
-// private to hangouts.
-template <typename C>
-class PubSubStateClient : public sigslot::has_slots<> {
- public:
-  // Gets ownership of the serializers, but not the client.
-  PubSubStateClient(const std::string& publisher_nick,
-                    PubSubClient* client,
-                    const QName& state_name,
-                    C default_state,
-                    PubSubStateKeySerializer* key_serializer,
-                    PubSubStateSerializer<C>* state_serializer)
-      : publisher_nick_(publisher_nick),
-        client_(client),
-        state_name_(state_name),
-        default_state_(default_state) {
-    key_serializer_.reset(key_serializer);
-    state_serializer_.reset(state_serializer);
-    client_->SignalItems.connect(
-        this, &PubSubStateClient<C>::OnItems);
-    client_->SignalPublishResult.connect(
-        this, &PubSubStateClient<C>::OnPublishResult);
-    client_->SignalPublishError.connect(
-        this, &PubSubStateClient<C>::OnPublishError);
-    client_->SignalRetractResult.connect(
-        this, &PubSubStateClient<C>::OnRetractResult);
-    client_->SignalRetractError.connect(
-        this, &PubSubStateClient<C>::OnRetractError);
-  }
-
-  virtual ~PubSubStateClient() {}
-
-  virtual void Publish(const std::string& published_nick,
-                       const C& state,
-                       std::string* task_id_out) {
-    std::string key = key_serializer_->GetKey(publisher_nick_, published_nick);
-    std::string itemid = state_name_.LocalPart() + ":" + key;
-    if (StatesEqual(state, default_state_)) {
-      client_->RetractItem(itemid, task_id_out);
-    } else {
-      XmlElement* state_elem = state_serializer_->Write(state_name_, state);
-      state_elem->AddAttr(QN_NICK, published_nick);
-      client_->PublishItem(itemid, state_elem, task_id_out);
-    }
-  };
-
-  sigslot::signal1<const PubSubStateChange<C>&> SignalStateChange;
-  // Signal (task_id, item).  item is NULL for retract.
-  sigslot::signal2<const std::string&,
-                   const XmlElement*> SignalPublishResult;
-  // Signal (task_id, item, error stanza).  item is NULL for retract.
-  sigslot::signal3<const std::string&,
-                   const XmlElement*,
-                   const XmlElement*> SignalPublishError;
-
- protected:
-  // return false if retracted item (no info or state given)
-  virtual bool ParseStateItem(const PubSubItem& item,
-                              StateItemInfo* info_out,
-                              bool* state_out) {
-    const XmlElement* state_elem = item.elem->FirstNamed(state_name_);
-    if (state_elem == NULL) {
-      return false;
-    }
-
-    info_out->publisher_nick = GetPublisherNickFromPubSubItem(item.elem);
-    info_out->published_nick = state_elem->Attr(QN_NICK);
-    *state_out = state_serializer_->Parse(state_elem);
-    return true;
-  };
-
-  virtual bool StatesEqual(C state1, C state2) {
-    return state1 == state2;
-  }
-
-  PubSubClient* client() { return client_; }
-
- private:
-  void OnItems(PubSubClient* pub_sub_client,
-               const std::vector<PubSubItem>& items) {
-    for (std::vector<PubSubItem>::const_iterator item = items.begin();
-         item != items.end(); ++item) {
-      OnItem(*item);
-    }
-  }
-
-  void OnItem(const PubSubItem& item) {
-    const std::string& itemid = item.itemid;
-    StateItemInfo info;
-    C new_state;
-
-    bool retracted = !ParseStateItem(item, &info, &new_state);
-    if (retracted) {
-      bool known_itemid =
-          (info_by_itemid_.find(itemid) != info_by_itemid_.end());
-      if (!known_itemid) {
-        // Nothing to retract, and nothing to publish.
-        // Probably a different state type.
-        return;
-      } else {
-        info = info_by_itemid_[itemid];
-        info_by_itemid_.erase(itemid);
-        new_state = default_state_;
-      }
-    } else {
-      // TODO: Assert new key matches the known key. It
-      // shouldn't change!
-      info_by_itemid_[itemid] = info;
-    }
-
-    std::string key = key_serializer_->GetKey(
-        info.publisher_nick, info.published_nick);
-    bool has_old_state = (state_by_key_.find(key) != state_by_key_.end());
-    C old_state = has_old_state ? state_by_key_[key] : default_state_;
-    if ((retracted && !has_old_state) || StatesEqual(new_state, old_state)) {
-      // Nothing change, so don't bother signalling.
-      return;
-    }
-
-    if (retracted || StatesEqual(new_state, default_state_)) {
-      // We treat a default state similar to a retract.
-      state_by_key_.erase(key);
-    } else {
-      state_by_key_[key] = new_state;
-    }
-
-    PubSubStateChange<C> change;
-    if (!retracted) {
-      // Retracts do not have publisher information.
-      change.publisher_nick = info.publisher_nick;
-    }
-    change.published_nick = info.published_nick;
-    change.old_state = old_state;
-    change.new_state = new_state;
-    SignalStateChange(change);
- }
-
-  void OnPublishResult(PubSubClient* pub_sub_client,
-                       const std::string& task_id,
-                       const XmlElement* item) {
-    SignalPublishResult(task_id, item);
-  }
-
-  void OnPublishError(PubSubClient* pub_sub_client,
-                      const std::string& task_id,
-                      const buzz::XmlElement* item,
-                      const buzz::XmlElement* stanza) {
-    SignalPublishError(task_id, item, stanza);
-  }
-
-  void OnRetractResult(PubSubClient* pub_sub_client,
-                       const std::string& task_id) {
-    // There's no point in differentiating between publish and retract
-    // errors, so we simplify by making them both signal a publish
-    // result.
-    const XmlElement* item = NULL;
-    SignalPublishResult(task_id, item);
-  }
-
-  void OnRetractError(PubSubClient* pub_sub_client,
-                      const std::string& task_id,
-                      const buzz::XmlElement* stanza) {
-    // There's no point in differentiating between publish and retract
-    // errors, so we simplify by making them both signal a publish
-    // error.
-    const XmlElement* item = NULL;
-    SignalPublishError(task_id, item, stanza);
-  }
-
-  std::string publisher_nick_;
-  PubSubClient* client_;
-  const QName state_name_;
-  C default_state_;
-  talk_base::scoped_ptr<PubSubStateKeySerializer> key_serializer_;
-  talk_base::scoped_ptr<PubSubStateSerializer<C> > state_serializer_;
-  // key => state
-  std::map<std::string, C> state_by_key_;
-  // itemid => StateItemInfo
-  std::map<std::string, StateItemInfo> info_by_itemid_;
-};
-
 class PresenterStateClient : public PubSubStateClient<bool> {
  public:
   PresenterStateClient(const std::string& publisher_nick,
@@ -336,15 +106,17 @@
       return false;
     }
 
-    info_out->publisher_nick = GetPublisherNickFromPubSubItem(item.elem);
+    info_out->publisher_nick =
+        client()->GetPublisherNickFromPubSubItem(item.elem);
     info_out->published_nick = presenter_elem->Attr(QN_NICK);
     *state_out = (presentation_item_elem->Attr(
         QN_PRESENTER_PRESENTATION_TYPE) != kNotPresenting);
     return true;
   }
 
-  virtual bool StatesEqual(bool state1, bool state2) {
-    return false;  // Make every item trigger an event, even if state doesn't change.
+  virtual bool StatesEqual(const bool& state1, const bool& state2) {
+    // Make every item trigger an event, even if state doesn't change.
+    return false;
   }
 };
 
diff --git a/talk/xmpp/hangoutpubsubclient.h b/talk/xmpp/hangoutpubsubclient.h
index a9986db..2fcd691 100644
--- a/talk/xmpp/hangoutpubsubclient.h
+++ b/talk/xmpp/hangoutpubsubclient.h
@@ -37,6 +37,7 @@
 #include "talk/base/sigslotrepeater.h"
 #include "talk/xmpp/jid.h"
 #include "talk/xmpp/pubsubclient.h"
+#include "talk/xmpp/pubsubstateclient.h"
 
 // Gives a high-level API for MUC call PubSub needs such as
 // presenter state, recording state, mute state, and remote mute.
@@ -47,30 +48,6 @@
 class XmlElement;
 class XmppTaskParentInterface;
 
-// To handle retracts correctly, we need to remember certain details
-// about an item.  We could just cache the entire XML element, but
-// that would take more memory and require re-parsing.
-struct StateItemInfo {
-  std::string published_nick;
-  std::string publisher_nick;
-};
-
-// Represents a PubSub state change.  Usually, the key is the nick,
-// but not always.  It's a per-state-type thing.  Currently documented
-// at https://docs.google.com/a/google.com/document/d/
-// 1QyHu_ufyVdf0VICdfc_DtJbrOdrdIUm4eM73RZqnivI/edit?hl=en_US
-template <typename C>
-struct PubSubStateChange {
-  // The nick of the user changing the state.
-  std::string publisher_nick;
-  // The nick of the user whose state is changing.
-  std::string published_nick;
-  C old_state;
-  C new_state;
-};
-
-template <typename C> class PubSubStateClient;
-
 // A client tied to a specific MUC jid and local nick.  Provides ways
 // to get updates and publish state and events.  Must call
 // RequestAll() to start getting updates.
diff --git a/talk/xmpp/pubsubclient.cc b/talk/xmpp/pubsubclient.cc
index 8d6d4c4..b627587 100644
--- a/talk/xmpp/pubsubclient.cc
+++ b/talk/xmpp/pubsubclient.cc
@@ -134,4 +134,13 @@
   SignalRetractError(this, retract_task->task_id(), stanza);
 }
 
+
+const std::string PubSubClient::GetPublisherNickFromPubSubItem(
+    const XmlElement* item_elem) {
+  if (item_elem == NULL) {
+    return "";
+  }
+
+  return Jid(item_elem->Attr(QN_ATTR_PUBLISHER)).resource();
+}
 }  // namespace buzz
diff --git a/talk/xmpp/pubsubclient.h b/talk/xmpp/pubsubclient.h
index 099765a..f0cd7a9 100644
--- a/talk/xmpp/pubsubclient.h
+++ b/talk/xmpp/pubsubclient.h
@@ -101,6 +101,9 @@
   void RetractItem(const std::string& itemid,
                    std::string* task_id_out);
 
+  // Get the publisher nick if it exists from the pubsub item.
+  const std::string GetPublisherNickFromPubSubItem(const XmlElement* item_elem);
+
  private:
   void OnRequestError(IqTask* task,
                       const XmlElement* stanza);
diff --git a/talk/xmpp/pubsubstateclient.cc b/talk/xmpp/pubsubstateclient.cc
new file mode 100644
index 0000000..5cd7b1a
--- /dev/null
+++ b/talk/xmpp/pubsubstateclient.cc
@@ -0,0 +1,42 @@
+/*
+ * libjingle
+ * Copyright 2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/xmpp/pubsubstateclient.h"
+
+namespace buzz {
+
+std::string PublishedNickKeySerializer::GetKey(
+    const std::string& publisher_nick, const std::string& published_nick) {
+  return published_nick;
+}
+
+std::string PublisherAndPublishedNicksKeySerializer::GetKey(
+    const std::string& publisher_nick, const std::string& published_nick) {
+  return publisher_nick + ":" + published_nick;
+}
+
+}  // namespace buzz
diff --git a/talk/xmpp/pubsubstateclient.h b/talk/xmpp/pubsubstateclient.h
new file mode 100644
index 0000000..f38658d
--- /dev/null
+++ b/talk/xmpp/pubsubstateclient.h
@@ -0,0 +1,287 @@
+/*
+ * libjingle
+ * Copyright 2011, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  1. Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *  2. Redistributions in binary form must reproduce the above copyright notice,
+ *     this list of conditions and the following disclaimer in the documentation
+ *     and/or other materials provided with the distribution.
+ *  3. The name of the author may not be used to endorse or promote products
+ *     derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_XMPP_PUBSUBSTATECLIENT_H_
+#define TALK_XMPP_PUBSUBSTATECLIENT_H_
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/sigslot.h"
+#include "talk/base/sigslotrepeater.h"
+#include "talk/xmpp/constants.h"
+#include "talk/xmpp/jid.h"
+#include "talk/xmpp/pubsubclient.h"
+#include "talk/xmllite/qname.h"
+#include "talk/xmllite/xmlelement.h"
+
+namespace buzz {
+
+// To handle retracts correctly, we need to remember certain details
+// about an item.  We could just cache the entire XML element, but
+// that would take more memory and require re-parsing.
+struct StateItemInfo {
+  std::string published_nick;
+  std::string publisher_nick;
+};
+
+// Represents a PubSub state change.  Usually, the key is the nick,
+// but not always.  It's a per-state-type thing.  Look below on how keys are
+// computed.
+template <typename C>
+struct PubSubStateChange {
+  // The nick of the user changing the state.
+  std::string publisher_nick;
+  // The nick of the user whose state is changing.
+  std::string published_nick;
+  C old_state;
+  C new_state;
+};
+
+// Knows how to handle specific states and XML.
+template <typename C>
+class PubSubStateSerializer {
+ public:
+  virtual ~PubSubStateSerializer() {}
+  virtual XmlElement* Write(const QName& state_name, const C& state) = 0;
+  virtual void Parse(const XmlElement* state_elem, C* state_out) = 0;
+};
+
+// Knows how to create "keys" for states, which determines their
+// uniqueness.  Most states are per-nick, but block is
+// per-blocker-and-blockee.  This is independent of itemid, especially
+// in the case of presenter state.
+class PubSubStateKeySerializer {
+ public:
+  virtual ~PubSubStateKeySerializer() {}
+  virtual std::string GetKey(const std::string& publisher_nick,
+                             const std::string& published_nick) = 0;
+};
+
+class PublishedNickKeySerializer : public PubSubStateKeySerializer {
+ public:
+  virtual std::string GetKey(const std::string& publisher_nick,
+                             const std::string& published_nick);
+};
+
+class PublisherAndPublishedNicksKeySerializer
+  : public PubSubStateKeySerializer {
+ public:
+  virtual std::string GetKey(const std::string& publisher_nick,
+                             const std::string& published_nick);
+};
+
+// Adapts PubSubClient to be specifically suited for pub sub call
+// states.  Signals state changes and keeps track of keys, which are
+// normally nicks.
+template <typename C>
+class PubSubStateClient : public sigslot::has_slots<> {
+ public:
+  // Gets ownership of the serializers, but not the client.
+  PubSubStateClient(const std::string& publisher_nick,
+                    PubSubClient* client,
+                    const QName& state_name,
+                    C default_state,
+                    PubSubStateKeySerializer* key_serializer,
+                    PubSubStateSerializer<C>* state_serializer)
+      : publisher_nick_(publisher_nick),
+        client_(client),
+        state_name_(state_name),
+        default_state_(default_state) {
+    key_serializer_.reset(key_serializer);
+    state_serializer_.reset(state_serializer);
+    client_->SignalItems.connect(
+        this, &PubSubStateClient<C>::OnItems);
+    client_->SignalPublishResult.connect(
+        this, &PubSubStateClient<C>::OnPublishResult);
+    client_->SignalPublishError.connect(
+        this, &PubSubStateClient<C>::OnPublishError);
+    client_->SignalRetractResult.connect(
+        this, &PubSubStateClient<C>::OnRetractResult);
+    client_->SignalRetractError.connect(
+        this, &PubSubStateClient<C>::OnRetractError);
+  }
+
+  virtual ~PubSubStateClient() {}
+
+  virtual void Publish(const std::string& published_nick,
+                       const C& state,
+                       std::string* task_id_out) {
+    std::string key = key_serializer_->GetKey(publisher_nick_, published_nick);
+    std::string itemid = state_name_.LocalPart() + ":" + key;
+    if (StatesEqual(state, default_state_)) {
+      client_->RetractItem(itemid, task_id_out);
+    } else {
+      XmlElement* state_elem = state_serializer_->Write(state_name_, state);
+      state_elem->AddAttr(QN_NICK, published_nick);
+      client_->PublishItem(itemid, state_elem, task_id_out);
+    }
+  }
+
+  sigslot::signal1<const PubSubStateChange<C>&> SignalStateChange;
+  // Signal (task_id, item).  item is NULL for retract.
+  sigslot::signal2<const std::string&,
+  const XmlElement*> SignalPublishResult;
+  // Signal (task_id, item, error stanza).  item is NULL for retract.
+  sigslot::signal3<const std::string&,
+  const XmlElement*,
+  const XmlElement*> SignalPublishError;
+
+ protected:
+  // return false if retracted item (no info or state given)
+  virtual bool ParseStateItem(const PubSubItem& item,
+                              StateItemInfo* info_out,
+                              C* state_out) {
+    const XmlElement* state_elem = item.elem->FirstNamed(state_name_);
+    if (state_elem == NULL) {
+      return false;
+    }
+
+    info_out->publisher_nick =
+        client_->GetPublisherNickFromPubSubItem(item.elem);
+    info_out->published_nick = state_elem->Attr(QN_NICK);
+    state_serializer_->Parse(state_elem, state_out);
+    return true;
+  }
+
+  virtual bool StatesEqual(const C& state1, const C& state2) {
+    return state1 == state2;
+  }
+
+  PubSubClient* client() { return client_; }
+  const QName& state_name() { return state_name_; }
+
+ private:
+  void OnItems(PubSubClient* pub_sub_client,
+               const std::vector<PubSubItem>& items) {
+    for (std::vector<PubSubItem>::const_iterator item = items.begin();
+         item != items.end(); ++item) {
+      OnItem(*item);
+    }
+  }
+
+  void OnItem(const PubSubItem& item) {
+    const std::string& itemid = item.itemid;
+    StateItemInfo info;
+    C new_state;
+
+    bool retracted = !ParseStateItem(item, &info, &new_state);
+    if (retracted) {
+      bool known_itemid =
+          (info_by_itemid_.find(itemid) != info_by_itemid_.end());
+      if (!known_itemid) {
+        // Nothing to retract, and nothing to publish.
+        // Probably a different state type.
+        return;
+      } else {
+        info = info_by_itemid_[itemid];
+        info_by_itemid_.erase(itemid);
+        new_state = default_state_;
+      }
+    } else {
+      // TODO: Assert new key matches the known key. It
+      // shouldn't change!
+      info_by_itemid_[itemid] = info;
+    }
+
+    std::string key = key_serializer_->GetKey(
+        info.publisher_nick, info.published_nick);
+    bool has_old_state = (state_by_key_.find(key) != state_by_key_.end());
+    C old_state = has_old_state ? state_by_key_[key] : default_state_;
+    if ((retracted && !has_old_state) || StatesEqual(new_state, old_state)) {
+      // Nothing change, so don't bother signalling.
+      return;
+    }
+
+    if (retracted || StatesEqual(new_state, default_state_)) {
+      // We treat a default state similar to a retract.
+      state_by_key_.erase(key);
+    } else {
+      state_by_key_[key] = new_state;
+    }
+
+    PubSubStateChange<C> change;
+    if (!retracted) {
+      // Retracts do not have publisher information.
+      change.publisher_nick = info.publisher_nick;
+    }
+    change.published_nick = info.published_nick;
+    change.old_state = old_state;
+    change.new_state = new_state;
+    SignalStateChange(change);
+  }
+
+  void OnPublishResult(PubSubClient* pub_sub_client,
+                       const std::string& task_id,
+                       const XmlElement* item) {
+    SignalPublishResult(task_id, item);
+  }
+
+  void OnPublishError(PubSubClient* pub_sub_client,
+                      const std::string& task_id,
+                      const buzz::XmlElement* item,
+                      const buzz::XmlElement* stanza) {
+    SignalPublishError(task_id, item, stanza);
+  }
+
+  void OnRetractResult(PubSubClient* pub_sub_client,
+                       const std::string& task_id) {
+    // There's no point in differentiating between publish and retract
+    // errors, so we simplify by making them both signal a publish
+    // result.
+    const XmlElement* item = NULL;
+    SignalPublishResult(task_id, item);
+  }
+
+  void OnRetractError(PubSubClient* pub_sub_client,
+                      const std::string& task_id,
+                      const buzz::XmlElement* stanza) {
+    // There's no point in differentiating between publish and retract
+    // errors, so we simplify by making them both signal a publish
+    // error.
+    const XmlElement* item = NULL;
+    SignalPublishError(task_id, item, stanza);
+  }
+
+  std::string publisher_nick_;
+  PubSubClient* client_;
+  const QName state_name_;
+  C default_state_;
+  talk_base::scoped_ptr<PubSubStateKeySerializer> key_serializer_;
+  talk_base::scoped_ptr<PubSubStateSerializer<C> > state_serializer_;
+  // key => state
+  std::map<std::string, C> state_by_key_;
+  // itemid => StateItemInfo
+  std::map<std::string, StateItemInfo> info_by_itemid_;
+
+  DISALLOW_COPY_AND_ASSIGN(PubSubStateClient);
+};
+}  // namespace buzz
+
+#endif  // TALK_XMPP_PUBSUBSTATECLIENT_H_