Import Cobalt 24.lts.10.1032622
diff --git a/build/toolchain/win/msvc_toolchain.gni b/build/toolchain/win/msvc_toolchain.gni
index 92c98d4..01c3ebb 100644
--- a/build/toolchain/win/msvc_toolchain.gni
+++ b/build/toolchain/win/msvc_toolchain.gni
@@ -12,6 +12,8 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+assert(use_cobalt_customizations)
+
 import("//build/toolchain/cc_wrapper.gni")
 import("//build/toolchain/toolchain.gni")
 
@@ -19,15 +21,19 @@
 
 template("msvc_toolchain") {
   # Write the environment variables file in the out directory.
-  required_environment_variables = ["SYSTEMROOT", "TEMP", "TMP"]
+  required_environment_variables = [
+    "SYSTEMROOT",
+    "TEMP",
+    "TMP",
+  ]
   optional_environment_variables = [
     "INCLUDE",
     "LIB",
     "PATH",
     "PATHEXT",
-    "XEDK", # TODO: What does this do?
-    "IS_DOCKER", # needed for ninja to invoke docker-specific logic
-    "IS_CI",     # needed for ninja to exclude some logic on GKE
+    "XEDK",  # TODO: What does this do?
+    "IS_DOCKER",  # needed for ninja to invoke docker-specific logic
+    "IS_CI",  # needed for ninja to exclude some logic on GKE
 
     # The remaining variables should be explicitly enumerated.
     # "cell_.*",
@@ -51,7 +57,11 @@
 
   nul = "$0x00"
   env_block = string_join(nul, environment_key_value_pairs) + nul
-  write_file("$root_build_dir/environment.$target_cpu", env_block)
+
+  # If the default toolchain shares its name with another toolchain,
+  # there could be a deadlock where both toolchains try to write to the same environment file.
+  # See b/297227714 for more context.
+  write_file("$root_build_dir/$target_name/environment.$target_cpu", env_block)
 
   toolchain(target_name) {
     # When invoking this toolchain not as the default one, these args will be
@@ -70,8 +80,7 @@
     # Object files go in this directory.
     object_subdir = "{{target_out_dir}}/{{label_name}}"
 
-    env = "environment.$target_cpu"
-
+    env = "$target_name/environment.$target_cpu"
     cl = invoker.cl
     lib = invoker.lib
     link = invoker.link
@@ -119,7 +128,7 @@
     tool("asm") {
       description = "ASM {{output}}"
       outputs = [ "$object_subdir/{{source_name_part}}.obj" ]
-      command ="$env_wrapper$asm /nologo /Fo{{output}} /c {{defines}} {{include_dirs}} {{asmflags}} {{source}}"
+      command = "$env_wrapper$asm /nologo /Fo{{output}} /c {{defines}} {{include_dirs}} {{asmflags}} {{source}}"
     }
 
     sys_lib_flags = "${invoker.sys_lib_flags} "  # Note trailing space.
@@ -163,9 +172,7 @@
       ]
       link_output = libname
       depend_output = libname
-      runtime_outputs = [
-        dllname,
-      ]
+      runtime_outputs = [ dllname ]
 
       # Since the above commands only updates the .lib file when it changes, ask
       # Ninja to check if the timestamp actually changed to know if downstream
@@ -189,9 +196,7 @@
       default_output_extension = ".dll"
       default_output_dir = "{{root_out_dir}}"
       description = "LINK_MODULE(DLL) {{output}}"
-      outputs = [
-        dllname,
-      ]
+      outputs = [ dllname ]
       runtime_outputs = outputs
 
       # The use of inputs_newline is to work around a fixed per-line buffer
@@ -210,9 +215,7 @@
       default_output_extension = ".exe"
       default_output_dir = "{{root_out_dir}}"
       description = "LINK {{output}}"
-      outputs = [
-        exename,
-      ]
+      outputs = [ exename ]
       runtime_outputs = outputs
 
       # The use of inputs_newline is to work around a fixed per-line buffer
diff --git a/cobalt/version.h b/cobalt/version.h
index 517e7d5..bd8a169 100644
--- a/cobalt/version.h
+++ b/cobalt/version.h
@@ -35,6 +35,6 @@
 //                  release is cut.
 //.
 
-#define COBALT_VERSION "24.lts.5"
+#define COBALT_VERSION "24.lts.10"
 
 #endif  // COBALT_VERSION_H_
diff --git a/docker/docsite/Dockerfile b/docker/docsite/Dockerfile
index 2e164c8..6b1bd9d 100644
--- a/docker/docsite/Dockerfile
+++ b/docker/docsite/Dockerfile
@@ -46,10 +46,7 @@
 RUN mkdir /project_out_dir \
     && chown ${USER:-defaultuser}:defaultgroup /project_out_dir
 
-<<<<<<< HEAD
 RUN git config --global --add safe.directory /code
-=======
->>>>>>> 77f6a7c80d7 (Fix git config for /code in Linux containers (#611))
 
 COPY Gemfile /app/Gemfile
 # Note: This file was generated by running a working version of this Docker
diff --git a/starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaCodecBridge.java b/starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaCodecBridge.java
index 8b179c9..8db14f6 100644
--- a/starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaCodecBridge.java
+++ b/starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaCodecBridge.java
@@ -796,7 +796,7 @@
       String codecName = mMediaCodec.getName();
       Log.w(TAG, "calling MediaCodec.release() on " + codecName);
       mMediaCodec.release();
-    } catch (IllegalStateException e) {
+    } catch (Exception e) {
       // The MediaCodec is stuck in a wrong state, possibly due to losing
       // the surface.
       Log.e(TAG, "Cannot release media codec", e);
@@ -875,7 +875,7 @@
     }
     try {
       mMediaCodec.stop();
-    } catch (IllegalStateException e) {
+    } catch (Exception e) {
       Log.e(TAG, "Failed to stop MediaCodec", e);
     }
   }
diff --git a/starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaDrmBridge.java b/starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaDrmBridge.java
index ffcea69..d4a12a5 100644
--- a/starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaDrmBridge.java
+++ b/starboard/android/apk/app/src/main/java/dev/cobalt/media/MediaDrmBridge.java
@@ -706,7 +706,6 @@
           String.format("Successfully closed session (%s)", bytesToHexString(sessionId.array())));
     }
     mSessionIds.clear();
-    mSessionIds = null;
 
     // Close mMediaCryptoSession if it's open.
     if (mMediaCryptoSession != null) {
diff --git a/starboard/android/shared/test_filters.py b/starboard/android/shared/test_filters.py
index 641ae2b..ae8fbb6 100644
--- a/starboard/android/shared/test_filters.py
+++ b/starboard/android/shared/test_filters.py
@@ -86,15 +86,9 @@
         # TODO: Filter this test on a per-device basis.
         'SbMediaCanPlayMimeAndKeySystem.MinimumSupport',
 
-        # TODO: b/289281412 Make this test work on lab devices consistently.
-        'SbPlayerWriteSampleTests/SbPlayerWriteSampleTest.PartialAudio/*',
-
         # TODO: b/292319097 Make this test work on lab devices consistently.
         'SbPlayerTest.MaxVideoCapabilities',
 
-        # TODO: b/292409536 Make this test fork on lab devices consistently.
-        'SbPlayerWriteSampleTests/SbPlayerWriteSampleTest.PartialAudioDiscardAll/*',
-
         # TODO: b/280432564 Make this test work on lab devices consistently.
         'SbAudioSinkTest.ContinuousAppend',
     ],
diff --git a/starboard/shared/starboard/player/filter/audio_frame_discarder.cc b/starboard/shared/starboard/player/filter/audio_frame_discarder.cc
index 7b8d192..b1ad4fb 100644
--- a/starboard/shared/starboard/player/filter/audio_frame_discarder.cc
+++ b/starboard/shared/starboard/player/filter/audio_frame_discarder.cc
@@ -23,13 +23,7 @@
 namespace filter {
 
 void AudioFrameDiscarder::OnInputBuffers(const InputBuffers& input_buffers) {
-  if (input_buffer_infos_.size() >= kMaxNumberOfPendingInputBufferInfos) {
-    // This shouldn't happen as it's DCHECKed at the end of this function. Add
-    // an extra check here to ensure that |input_buffer_infos_| won't grow
-    // without bound, which can lead to OOM in production.
-    return;
-  }
-
+  ScopedLock lock(mutex_);
   for (auto&& input_buffer : input_buffers) {
     SB_DCHECK(input_buffer);
     SB_DCHECK(input_buffer->sample_type() == kSbMediaTypeAudio);
@@ -41,6 +35,8 @@
     });
   }
 
+  // Add a DCheck here to ensure that |input_buffer_infos_| won't grow
+  // without bound, which can lead to OOM.
   SB_DCHECK(input_buffer_infos_.size() < kMaxNumberOfPendingInputBufferInfos);
 }
 
@@ -49,32 +45,46 @@
     scoped_refptr<DecodedAudio>* decoded_audio) {
   SB_DCHECK(decoded_audio);
   SB_DCHECK(*decoded_audio);
-  // TODO: Comment out the SB_DCHECK due to b/274021285. We can re-enable it
-  // after b/274021285 is resolved.
-  // SB_DCHECK(!input_buffer_infos_.empty());
 
-  if (input_buffer_infos_.empty()) {
-    SB_LOG(WARNING) << "Inconsistent number of audio decoder outputs. Received "
-                       "outputs when input buffer list is empty.";
+  InputBufferInfo input_info;
+  {
+    ScopedLock lock(mutex_);
+    SB_DCHECK(!input_buffer_infos_.empty());
+
+    if (input_buffer_infos_.empty()) {
+      SB_LOG(WARNING)
+          << "Inconsistent number of audio decoder outputs. Received "
+             "outputs when input buffer list is empty.";
+      return;
+    }
+
+    input_info = input_buffer_infos_.front();
+    input_buffer_infos_.pop();
+  }
+
+  // We accept a small offset due to the precision of computation. If the
+  // outputs have different timestamps than inputs, discarded durations will be
+  // ignored.
+  const SbTimeMonotonic kTimestampOffset = 10;
+  if (std::abs(input_info.timestamp - (*decoded_audio)->timestamp()) >
+      kTimestampOffset) {
+    SB_LOG(WARNING) << "Inconsistent timestamps between InputBuffer (@"
+                    << input_info.timestamp << ") and DecodedAudio (@"
+                    << (*decoded_audio)->timestamp() << ").";
     return;
   }
 
-  auto info = input_buffer_infos_.front();
-  SB_LOG_IF(WARNING, info.timestamp != (*decoded_audio)->timestamp())
-      << "Inconsistent timestamps between InputBuffer (@" << info.timestamp
-      << ") and DecodedAudio (@" << (*decoded_audio)->timestamp() << ").";
-  input_buffer_infos_.pop();
-
   (*decoded_audio)
       ->AdjustForDiscardedDurations(sample_rate,
-                                    info.discarded_duration_from_front,
-                                    info.discarded_duration_from_back);
+                                    input_info.discarded_duration_from_front,
+                                    input_info.discarded_duration_from_back);
   // `(*decoded_audio)->frames()` might be 0 here.  We don't set it to nullptr
   // in this case so the DecodedAudio instance is always valid (but might be
   // empty).
 }
 
 void AudioFrameDiscarder::OnDecodedAudioEndOfStream() {
+  ScopedLock lock(mutex_);
   // |input_buffer_infos_| can have extra elements when the decoder skip outputs
   // due to errors (like invalid inputs).
   SB_LOG_IF(INFO, !input_buffer_infos_.empty())
@@ -83,6 +93,7 @@
 }
 
 void AudioFrameDiscarder::Reset() {
+  ScopedLock lock(mutex_);
   input_buffer_infos_ = std::queue<InputBufferInfo>();
 }
 
diff --git a/starboard/shared/starboard/player/filter/audio_frame_discarder.h b/starboard/shared/starboard/player/filter/audio_frame_discarder.h
index a1f4f37..80f5fbe 100644
--- a/starboard/shared/starboard/player/filter/audio_frame_discarder.h
+++ b/starboard/shared/starboard/player/filter/audio_frame_discarder.h
@@ -17,6 +17,7 @@
 
 #include <queue>
 
+#include "starboard/common/mutex.h"
 #include "starboard/common/ref_counted.h"
 #include "starboard/shared/internal_only.h"
 #include "starboard/shared/starboard/player/decoded_audio_internal.h"
@@ -35,8 +36,6 @@
 // corresponding InputBuffer object isn't available at the time.
 // This class assumes that there is exact one DecodedAudio object produced for
 // one InputBuffer object, which may not always be the case.
-// TODO(b/274021285): Ensure that the class works when there isn't a 1:1
-//                    relationship between DecodedAudio and InputBuffer.
 class AudioFrameDiscarder {
  public:
   void OnInputBuffers(const InputBuffers& input_buffers);
@@ -55,6 +54,7 @@
 
   static constexpr size_t kMaxNumberOfPendingInputBufferInfos = 128;
 
+  Mutex mutex_;
   std::queue<InputBufferInfo> input_buffer_infos_;
 };
 
diff --git a/starboard/shared/starboard/player/filter/testing/audio_decoder_test.cc b/starboard/shared/starboard/player/filter/testing/audio_decoder_test.cc
index b9500df..4b23321 100644
--- a/starboard/shared/starboard/player/filter/testing/audio_decoder_test.cc
+++ b/starboard/shared/starboard/player/filter/testing/audio_decoder_test.cc
@@ -185,6 +185,7 @@
 
     last_input_buffer_ = GetAudioInputBuffer(index);
     audio_decoder_->Decode({last_input_buffer_}, consumed_cb());
+    written_inputs_.push_back(last_input_buffer_);
   }
 
   void WriteSingleInput(size_t index,
@@ -200,6 +201,7 @@
     last_input_buffer_ = GetAudioInputBuffer(
         index, discarded_duration_from_front, discarded_duration_from_back);
     audio_decoder_->Decode({last_input_buffer_}, consumed_cb());
+    written_inputs_.push_back(last_input_buffer_);
   }
 
   // This has to be called when OnOutput() is called.
@@ -228,6 +230,11 @@
       ASSERT_LT(decoded_audios_.back()->timestamp(),
                 local_decoded_audio->timestamp());
     }
+    if (!using_stub_decoder_ && invalid_inputs_.empty()) {
+      ASSERT_NEAR(local_decoded_audio->timestamp(),
+                  written_inputs_.front()->timestamp(), 5);
+      written_inputs_.pop_front();
+    }
     decoded_audios_.push_back(local_decoded_audio);
     *decoded_audio = local_decoded_audio;
   }
@@ -428,6 +435,7 @@
 
   bool can_accept_more_input_ = true;
   scoped_refptr<InputBuffer> last_input_buffer_;
+  std::deque<scoped_refptr<InputBuffer>> written_inputs_;
   std::vector<scoped_refptr<DecodedAudio>> decoded_audios_;
 
   bool eos_written_ = false;