Import Cobalt 12.88774

Change-Id: Id685e20e1b1f8fec13607b39f552fad3cc76ebae
diff --git a/src/starboard/shared/alsa/alsa_audio_sink_type.cc b/src/starboard/shared/alsa/alsa_audio_sink_type.cc
index 25688a5..e0cbd79 100644
--- a/src/starboard/shared/alsa/alsa_audio_sink_type.cc
+++ b/src/starboard/shared/alsa/alsa_audio_sink_type.cc
@@ -99,6 +99,11 @@
   }
 #endif  // SB_API_VERSION >= 4
 
+  void SetVolume(double volume) SB_OVERRIDE {
+    ScopedLock lock(mutex_);
+    volume_ = volume;
+  }
+
   bool is_valid() { return playback_handle_ != NULL; }
 
  private:
@@ -124,6 +129,7 @@
   void* context_;
 
   double playback_rate_;
+  double volume_;
   std::vector<uint8_t> resample_buffer_;
 
   int channels_;
@@ -157,6 +163,7 @@
     void* context)
     : type_(type),
       playback_rate_(1.0),
+      volume_(1.0),
       resample_buffer_(channels * kFramesPerRequest *
                        GetSampleSize(sample_type)),
       channels_(channels),
@@ -272,6 +279,7 @@
 bool AlsaAudioSink::PlaybackLoop() {
   SB_DLOG(INFO) << "alsa::AlsaAudioSink enters playback loop";
 
+  // TODO: Also handle |volume_| here.
   double playback_rate = 1.0;
   for (;;) {
     int delayed_frame = AlsaGetBufferedFrames(playback_handle_);
diff --git a/src/starboard/shared/dlmalloc/system_get_used_cpu_memory.cc b/src/starboard/shared/dlmalloc/system_get_used_cpu_memory.cc
new file mode 100644
index 0000000..0fe2662
--- /dev/null
+++ b/src/starboard/shared/dlmalloc/system_get_used_cpu_memory.cc
@@ -0,0 +1,39 @@
+// Copyright 2017 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "starboard/memory.h"
+
+#include "starboard/shared/dlmalloc/page_internal.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+// Returns the number of bytes obtained from the system.  The total
+// number of bytes allocated by malloc, realloc etc., is less than this
+// value. Unlike mallinfo, this function returns only a precomputed
+// result, so can be called frequently to monitor memory consumption.
+// Even if locks are otherwise defined, this function does not use them,
+// so results might not be up to date.
+//
+// See http://gee.cs.oswego.edu/pub/misc/malloc.h for more details.
+size_t SB_ALLOCATOR(malloc_footprint)();
+
+#ifdef __cplusplus
+}  // extern "C"
+#endif
+
+int64_t SbSystemGetUsedCPUMemory() {
+  return static_cast<int64_t>(SB_ALLOCATOR(malloc_footprint)());
+}
diff --git a/src/starboard/shared/linux/dev_input/dev_input.cc b/src/starboard/shared/linux/dev_input/dev_input.cc
index bc3202e..d0bd7fe 100644
--- a/src/starboard/shared/linux/dev_input/dev_input.cc
+++ b/src/starboard/shared/linux/dev_input/dev_input.cc
@@ -24,6 +24,8 @@
 #include <vector>
 
 #include <algorithm>
+#include <cmath>
+#include <map>
 #include <string>
 
 #include "starboard/configuration.h"
@@ -44,15 +46,44 @@
 using ::starboard::shared::starboard::Application;
 
 typedef int FileDescriptor;
-const FileDescriptor kInvalidFd = -1;
-const int kKeyboardDeviceId = 1;
+const FileDescriptor kInvalidFd = -ENODEV;
+
+enum InputDeviceIds {
+  kKeyboardDeviceId = 1,
+  kGamepadDeviceId,
+};
+
+enum TouchPadPositionState {
+  kTouchPadPositionNone = 0,
+  kTouchPadPositionX = 1,
+  kTouchPadPositionY = 2,
+  kTouchPadPositionAll = kTouchPadPositionX | kTouchPadPositionY
+};
+
+struct InputDeviceInfo {
+  InputDeviceInfo() : fd(-1), touchpad_position_state(kTouchPadPositionNone) {}
+
+  // File descriptor open for the device
+  FileDescriptor fd;
+  // Absolute Axis info.
+  std::map<int, struct input_absinfo> axis_info;
+  std::map<int, float> axis_value;
+  int touchpad_position_state;
+};
+
+bool IsTouchpadPositionKnown(InputDeviceInfo* device_info) {
+  return device_info->touchpad_position_state == kTouchPadPositionAll;
+}
 
 // Private implementation of DevInput.
 class DevInputImpl : public DevInput {
  public:
   explicit DevInputImpl(SbWindow window);
+  DevInputImpl(SbWindow window, FileDescriptor wake_up_fd);
   ~DevInputImpl() SB_OVERRIDE;
 
+  void InitDevInputImpl(SbWindow window);
+
   Event* PollNextSystemEvent() SB_OVERRIDE;
   Event* WaitForSystemEventWithTimeout(SbTime time) SB_OVERRIDE;
   void WakeSystemEventWait() SB_OVERRIDE;
@@ -61,13 +92,27 @@
   // Converts an input_event into a kSbEventInput Application::Event. The caller
   // is responsible for deleting the returned event.
   Event* InputToApplicationEvent(const struct input_event& event,
+                                 InputDeviceInfo* device_info,
                                  int modifiers);
 
+  // Converts an input_event containing a key input into a kSbEventInput
+  // Application::Event. The caller is responsible for deleting the returned
+  // event.
+  Event* KeyInputToApplicationEvent(const struct input_event& event,
+                                    int modifiers);
+
+  // Converts an input_event containing an axis event into a kSbEventInput
+  // Application::Event. The caller is responsible for deleting the returned
+  // event.
+  Event* AxisInputToApplicationEvent(const struct input_event& event,
+                                     InputDeviceInfo* device_info,
+                                     int modifiers);
+
   // The window to attribute /dev/input events to.
   SbWindow window_;
 
   // A set of read-only file descriptor of keyboard input devices.
-  std::vector<FileDescriptor> keyboard_fds_;
+  std::vector<InputDeviceInfo> input_devices_;
 
   // A file descriptor of the write end of a pipe that can be written to from
   // any thread to wake up this waiter in a thread-safe manner.
@@ -76,6 +121,8 @@
   // A file descriptor of the read end of a pipe that this waiter will wait on
   // to allow it to be signalled safely from other threads.
   FileDescriptor wakeup_read_fd_;
+
+  FileDescriptor wake_up_fd_;
 };
 
 // Helper class to manage a file descriptor set.
@@ -126,16 +173,12 @@
     case KEY_PAGEDOWN:
       return kSbKeyNext;
     case KEY_LEFT:
-    case BTN_DPAD_LEFT:
       return kSbKeyLeft;
     case KEY_RIGHT:
-    case BTN_DPAD_RIGHT:
       return kSbKeyRight;
     case KEY_DOWN:
-    case BTN_DPAD_DOWN:
       return kSbKeyDown;
     case KEY_UP:
-    case BTN_DPAD_UP:
       return kSbKeyUp;
     case KEY_ESC:
       return kSbKeyEscape;
@@ -374,6 +417,49 @@
       return kSbKeyBrightnessDown;
     case KEY_BRIGHTNESSUP:
       return kSbKeyBrightnessUp;
+
+    // Gamepad buttons.
+    //   https://www.kernel.org/doc/Documentation/input/gamepad.txt
+    case BTN_TL:
+      return kSbKeyGamepadLeftTrigger;
+    case BTN_TR:
+      return kSbKeyGamepadRightTrigger;
+    case BTN_DPAD_DOWN:
+      return kSbKeyGamepadDPadDown;
+    case BTN_DPAD_UP:
+      return kSbKeyGamepadDPadUp;
+    case BTN_DPAD_LEFT:
+      return kSbKeyGamepadDPadLeft;
+    case BTN_DPAD_RIGHT:
+      return kSbKeyGamepadDPadRight;
+    // The mapping for the buttons below can vary from controller to controller.
+    // TODO: Include button mapping for controllers with different layout.
+    case BTN_B:
+      return kSbKeyGamepad1;
+    case BTN_C:
+      return kSbKeyGamepad2;
+    case BTN_A:
+      return kSbKeyGamepad3;
+    case BTN_X:
+      return kSbKeyGamepad4;
+    case BTN_Y:
+      return kSbKeyGamepadLeftBumper;
+    case BTN_Z:
+      return kSbKeyGamepadRightBumper;
+    case BTN_TL2:
+      return kSbKeyGamepad5;
+    case BTN_TR2:
+      return kSbKeyGamepad6;
+    case BTN_SELECT:
+      return kSbKeyGamepadLeftStick;
+    case BTN_START:
+      return kSbKeyGamepadRightStick;
+    case BTN_MODE:
+      return kSbKeyGamepadSystem;
+    case BTN_THUMBL:
+      return kSbKeyGamepad1;
+    case BTN_THUMBR:
+      return kSbKeyGamepad1;
   }
   SB_DLOG(WARNING) << "Unknown code: 0x" << std::hex << code;
   return kSbKeyUnknown;
@@ -408,21 +494,129 @@
   return !!(bitset.at(bit / 8) & (1 << (bit % 8)));
 }
 
-// Searches for the keyboard /dev/input devices, opens them and returns the file
-// descriptors that report keyboard events.
-std::vector<FileDescriptor> GetKeyboardFds() {
-  const char kDevicePath[] = "/dev/input";
-  SbDirectory directory = SbDirectoryOpen(kDevicePath, NULL);
-  std::vector<FileDescriptor> fds;
-  if (!SbDirectoryIsValid(directory)) {
-    SB_DLOG(ERROR) << __FUNCTION__ << ": No /dev/input support, "
-                   << "unable to open: " << kDevicePath;
-    return fds;
+bool IsAxisFlat(float median, const struct input_absinfo& axis_info) {
+  SB_DCHECK((axis_info.flat * 2) <= (axis_info.maximum - axis_info.minimum));
+  return (axis_info.flat != 0) && (axis_info.value > median - axis_info.flat) &&
+         (axis_info.value < median + axis_info.flat);
+}
+
+float GetAxisValue(const struct input_absinfo& axis_info) {
+  float median =
+      static_cast<float>(axis_info.maximum + axis_info.minimum) / 2.0f;
+  if (IsAxisFlat(median, axis_info))
+    return 0;
+  float range = static_cast<float>(axis_info.maximum - axis_info.minimum);
+  float radius = range / 2.0f;
+  // Scale the axis value to [-1, 1].
+  float axis_value = (static_cast<float>(axis_info.value) - median) / radius;
+
+  if (axis_info.flat != 0) {
+    // Calculate the flat value scaled to [0, 1].
+    float flat = static_cast<float>(axis_info.flat) / range;
+
+    int sign = axis_value < 0.0f ? -1 : 1;
+    // Rescale the range:
+    // [-1.0f, -flat] to [-1.0f, 0.0f] and [flat, 1.0f] to [0.0f, 1.0f].
+    axis_value = (axis_value - sign * flat) / (1 - flat);
+  }
+  return axis_value;
+}
+
+void GetInputDeviceAbsoluteAxisInfo(int axis,
+                                    const std::vector<uint8_t>& bits,
+                                    InputDeviceInfo* info) {
+  if (IsBitSet(bits, axis)) {
+    struct input_absinfo axis_info;
+    int result = ioctl(info->fd, EVIOCGABS(axis), &axis_info);
+    if (result < 0) {
+      return;
+    }
+    info->axis_info.insert(std::make_pair(axis, axis_info));
+    info->axis_value.insert(std::make_pair(axis, GetAxisValue(axis_info)));
+  }
+}
+
+void GetInputDeviceInfo(InputDeviceInfo* info) {
+  std::vector<uint8_t> axis_bits(BytesNeededForBitSet(KEY_MAX));
+  int result =
+      ioctl(info->fd, EVIOCGBIT(EV_ABS, axis_bits.size()), axis_bits.data());
+  if (result < 0) {
+    return;
+  }
+
+  GetInputDeviceAbsoluteAxisInfo(ABS_X, axis_bits, info);
+  GetInputDeviceAbsoluteAxisInfo(ABS_Y, axis_bits, info);
+  GetInputDeviceAbsoluteAxisInfo(ABS_Z, axis_bits, info);
+  GetInputDeviceAbsoluteAxisInfo(ABS_RZ, axis_bits, info);
+  GetInputDeviceAbsoluteAxisInfo(ABS_RX, axis_bits, info);
+  GetInputDeviceAbsoluteAxisInfo(ABS_RY, axis_bits, info);
+  GetInputDeviceAbsoluteAxisInfo(ABS_HAT0X, axis_bits, info);
+  GetInputDeviceAbsoluteAxisInfo(ABS_HAT0Y, axis_bits, info);
+  GetInputDeviceAbsoluteAxisInfo(ABS_MT_POSITION_X, axis_bits, info);
+  GetInputDeviceAbsoluteAxisInfo(ABS_MT_POSITION_Y, axis_bits, info);
+  GetInputDeviceAbsoluteAxisInfo(ABS_MT_TRACKING_ID, axis_bits, info);
+  // TODO: Handle multi-touch using ABS_MT_SLOT.
+}
+
+FileDescriptor OpenDeviceIfKeyboardOrGamepad(const char* path) {
+  FileDescriptor fd = open(path, O_RDONLY | O_NONBLOCK);
+  if (fd < 0) {
+    // Open can fail if the application doesn't have permission to access
+    // the input device directly.
+    return kInvalidFd;
   }
 
   std::vector<uint8_t> ev_bits(BytesNeededForBitSet(EV_CNT));
   std::vector<uint8_t> key_bits(BytesNeededForBitSet(KEY_MAX));
 
+  int result = ioctl(fd, EVIOCGBIT(0, ev_bits.size()), ev_bits.data());
+  if (result < 0) {
+    close(fd);
+    return kInvalidFd;
+  }
+
+  bool has_ev_key = IsBitSet(ev_bits, EV_KEY);
+  if (!has_ev_key) {
+    close(fd);
+    return kInvalidFd;
+  }
+
+  result = ioctl(fd, EVIOCGBIT(EV_KEY, key_bits.size()), key_bits.data());
+  if (result < 0) {
+    close(fd);
+    return kInvalidFd;
+  }
+
+  bool has_key_space = IsBitSet(key_bits, KEY_SPACE);
+  bool has_gamepad_button = IsBitSet(key_bits, BTN_GAMEPAD);
+  if (!has_key_space && !has_gamepad_button) {
+    // If it doesn't have a space key or gamepad button, it may be a mouse.
+    close(fd);
+    return kInvalidFd;
+  }
+
+  result = ioctl(fd, EVIOCGRAB, 1);
+  if (result != 0) {
+    SB_DLOG(ERROR) << __FUNCTION__ << ": "
+                   << "Unable to get exclusive access to \"" << path << "\".";
+    close(fd);
+    return kInvalidFd;
+  }
+  return fd;
+}
+
+// Searches for the keyboard and game controller /dev/input devices, opens them
+// and returns the device info with a file descriptor and absolute axis details.
+std::vector<InputDeviceInfo> GetInputDevices() {
+  const char kDevicePath[] = "/dev/input";
+  SbDirectory directory = SbDirectoryOpen(kDevicePath, NULL);
+  std::vector<InputDeviceInfo> input_devices;
+  if (!SbDirectoryIsValid(directory)) {
+    SB_DLOG(ERROR) << __FUNCTION__ << ": No /dev/input support, "
+                   << "unable to open: " << kDevicePath;
+    return input_devices;
+  }
+
   while (true) {
     SbDirectoryEntry entry;
     if (!SbDirectoryGetNext(directory, &entry)) {
@@ -438,60 +632,25 @@
       continue;
     }
 
-    FileDescriptor fd = open(path.c_str(), O_RDONLY | O_NONBLOCK);
-    if (fd < 0) {
-      SB_DLOG(ERROR) << __FUNCTION__ << ": Unable to open \"" << path << "\".";
+    FileDescriptor fd = OpenDeviceIfKeyboardOrGamepad(path.c_str());
+    if (fd == kInvalidFd) {
       continue;
     }
+    InputDeviceInfo info;
+    info.fd = fd;
+    GetInputDeviceInfo(&info);
 
-    int result = ioctl(fd, EVIOCGBIT(0, ev_bits.size()), ev_bits.data());
-
-    if (result < 0) {
-      close(fd);
-      continue;
-    }
-
-    bool has_ev_key = IsBitSet(ev_bits, EV_KEY);
-
-    if (!has_ev_key) {
-      close(fd);
-      continue;
-    }
-
-    result = ioctl(fd, EVIOCGBIT(EV_KEY, key_bits.size()), key_bits.data());
-
-    if (result < 0) {
-      close(fd);
-      continue;
-    }
-
-    bool has_key_space = IsBitSet(key_bits, KEY_SPACE);
-
-    if (!has_key_space) {
-      // If it doesn't have a space key, it may be a mouse
-      close(fd);
-      continue;
-    }
-
-    result = ioctl(fd, EVIOCGRAB, 1);
-    if (result != 0) {
-      SB_DLOG(ERROR) << __FUNCTION__ << ": "
-                     << "Unable to get exclusive access to \"" << path << "\".";
-      close(fd);
-      continue;
-    }
-
-    SB_DCHECK(fd != kInvalidFd);
-    fds.push_back(fd);
+    SB_DCHECK(info.fd != kInvalidFd);
+    input_devices.push_back(info);
   }
 
-  if (fds.empty()) {
+  if (input_devices.empty()) {
     SB_DLOG(ERROR) << __FUNCTION__ << ": No /dev/input support. "
-                   << "No keyboards available.";
+                   << "No keyboards or game controllers available.";
   }
 
   SbDirectoryClose(directory);
-  return fds;
+  return input_devices;
 }
 
 // Returns whether |key_code|'s bit is set in the bitmap |map|, assuming
@@ -513,14 +672,14 @@
   return 0;
 }
 
-// Polls the given keyboard file descriptor for an input_event. If there are no
+// Polls the given input file descriptor for an input_event. If there are no
 // bytes available, assumes that there is no input event to read. If it gets a
 // partial event, it will assume that it will be completed, and spins until it
 // receives an entire event.
-bool PollKeyboardEvent(FileDescriptor fd,
-                       struct input_event* out_event,
-                       int* out_modifiers) {
-  if (fd == kInvalidFd) {
+bool PollInputEvent(InputDeviceInfo* device_info,
+                    struct input_event* out_event,
+                    int* out_modifiers) {
+  if (device_info->fd == kInvalidFd) {
     return false;
   }
 
@@ -531,7 +690,7 @@
   size_t remaining = kEventSize;
   char* buffer = reinterpret_cast<char*>(out_event);
   while (remaining > 0) {
-    int bytes_read = read(fd, buffer, remaining);
+    int bytes_read = read(device_info->fd, buffer, remaining);
     if (bytes_read <= 0) {
       if (errno == EAGAIN || bytes_read == 0) {
         if (remaining == kEventSize) {
@@ -544,18 +703,18 @@
       }
 
       // Some unexpected type of read error occured.
-      SB_DLOG(ERROR) << __FUNCTION__ << ": Error reading keyboard: " << errno
+      SB_DLOG(ERROR) << __FUNCTION__ << ": Error reading input: " << errno
                      << " - " << strerror(errno);
       return false;
     }
 
-    SB_DCHECK(bytes_read <= remaining) << "bytes_read=" << bytes_read
-                                       << ", remaining=" << remaining;
+    SB_DCHECK(bytes_read <= remaining)
+        << "bytes_read=" << bytes_read << ", remaining=" << remaining;
     remaining -= bytes_read;
     buffer += bytes_read;
   }
 
-  if (out_event->type != EV_KEY) {
+  if ((out_event->type != EV_KEY) && (out_event->type != EV_ABS)) {
     return false;
   }
 
@@ -563,7 +722,7 @@
   int modifiers = 0;
   char map[(KEY_MAX / 8) + 1] = {0};
   errno = 0;
-  int result = ioctl(fd, EVIOCGKEY(sizeof(map)), map);
+  int result = ioctl(device_info->fd, EVIOCGKEY(sizeof(map)), map);
   if (result != -1) {
     modifiers |=
         GetModifier(KEY_LEFTSHIFT, KEY_RIGHTSHIFT, kSbKeyModifiersShift, map);
@@ -593,20 +752,35 @@
 
 // Also in starboard/shared/libevent/socket_waiter_internal.cc
 // TODO: Consider consolidating.
-int SetNonBlocking(int fd) {
+int SetNonBlocking(FileDescriptor fd) {
   int flags = fcntl(fd, F_GETFL, 0);
-  if (flags == -1)
+  if (flags == -1) {
     flags = 0;
+  }
   return fcntl(fd, F_SETFL, flags | O_NONBLOCK);
 }
 
 DevInputImpl::DevInputImpl(SbWindow window)
     : window_(window),
-      keyboard_fds_(GetKeyboardFds()),
+      input_devices_(GetInputDevices()),
       wakeup_write_fd_(kInvalidFd),
-      wakeup_read_fd_(kInvalidFd) {
+      wakeup_read_fd_(kInvalidFd),
+      wake_up_fd_(kInvalidFd) {
+  InitDevInputImpl(window);
+}
+
+DevInputImpl::DevInputImpl(SbWindow window, FileDescriptor wake_up_fd)
+    : window_(window),
+      input_devices_(GetInputDevices()),
+      wakeup_write_fd_(kInvalidFd),
+      wakeup_read_fd_(kInvalidFd),
+      wake_up_fd_(wake_up_fd) {
+  InitDevInputImpl(window);
+}
+
+void DevInputImpl::InitDevInputImpl(SbWindow window) {
   // Initialize wakeup pipe.
-  int fds[2] = {kInvalidFd, kInvalidFd};
+  FileDescriptor fds[2] = {kInvalidFd, kInvalidFd};
   int result = pipe(fds);
   SB_DCHECK(result == 0) << "result=" << result;
 
@@ -622,9 +796,8 @@
 }
 
 DevInputImpl::~DevInputImpl() {
-  for (std::vector<FileDescriptor>::const_iterator it = keyboard_fds_.begin();
-       it != keyboard_fds_.end(); ++it) {
-    close(*it);
+  for (const auto& device : input_devices_) {
+    close(device.fd);
   }
   CloseFdSafely(&wakeup_write_fd_);
   CloseFdSafely(&wakeup_read_fd_);
@@ -633,13 +806,12 @@
 DevInput::Event* DevInputImpl::PollNextSystemEvent() {
   struct input_event event;
   int modifiers = 0;
-  for (std::vector<FileDescriptor>::const_iterator it = keyboard_fds_.begin();
-       it != keyboard_fds_.end(); ++it) {
-    if (!PollKeyboardEvent(*it, &event, &modifiers)) {
+  for (auto& device : input_devices_) {
+    if (!PollInputEvent(&device, &event, &modifiers)) {
       continue;
     }
 
-    return InputToApplicationEvent(event, modifiers);
+    return InputToApplicationEvent(event, &device, modifiers);
   }
   return NULL;
 }
@@ -651,9 +823,13 @@
   }
 
   FdSet read_set;
-  for (std::vector<FileDescriptor>::const_iterator it = keyboard_fds_.begin();
-       it != keyboard_fds_.end(); ++it) {
-    read_set.Set(*it);
+  if (wake_up_fd_ != kInvalidFd) {
+    read_set.Set(wake_up_fd_);
+  }
+
+  for (std::vector<InputDeviceInfo>::const_iterator it = input_devices_.begin();
+       it != input_devices_.end(); ++it) {
+    read_set.Set(it->fd);
   }
   read_set.Set(wakeup_read_fd_);
 
@@ -693,13 +869,256 @@
   }
 }
 
-DevInput::Event* DevInputImpl::InputToApplicationEvent(
-    const struct input_event& event,
-    int modifiers) {
-  if (event.type != EV_KEY) {
+namespace {
+
+// Creates a key event for an analog button input.
+DevInput::Event* CreateAnalogButtonKeyEvent(SbWindow window,
+                                            float axis_value,
+                                            float previous_axis_value,
+                                            SbKey key,
+                                            SbKeyLocation location,
+                                            int modifiers,
+                                            const struct input_event& event) {
+  SbInputEventType previous_type =
+      (std::abs(previous_axis_value) > 0.5 ? kSbInputEventTypePress
+                                           : kSbInputEventTypeUnpress);
+  SbInputEventType type =
+      (std::abs(axis_value) > 0.5 ? kSbInputEventTypePress
+                                  : kSbInputEventTypeUnpress);
+  if (previous_type == type) {
+    // Key press/unpress state did not change.
     return NULL;
   }
 
+  SbInputData* data = new SbInputData();
+  SbMemorySet(data, 0, sizeof(*data));
+  data->window = window;
+  data->type = type;
+  data->device_type = kSbInputDeviceTypeGamepad;
+  data->device_id = kGamepadDeviceId;
+  data->key = key;
+  data->key_location = location;
+  data->key_modifiers = modifiers;
+  return new DevInput::Event(kSbEventTypeInput, data,
+                             &Application::DeleteDestructor<SbInputData>);
+}
+
+// Creates a move event with key for a stick input.
+DevInput::Event* CreateMoveEventWithKey(SbWindow window,
+                                        SbKey key,
+                                        SbKeyLocation location,
+                                        int modifiers,
+                                        const SbInputVector& input_vector) {
+  SbInputData* data = new SbInputData();
+  SbMemorySet(data, 0, sizeof(*data));
+
+  data->window = window;
+  data->type = kSbInputEventTypeMove;
+  data->device_type = kSbInputDeviceTypeGamepad;
+  data->device_id = kGamepadDeviceId;
+
+  data->key = key;
+  data->key_location = location;
+  data->key_modifiers = modifiers;
+  data->position = input_vector;
+#if SB_API_VERSION >= SB_POINTER_INPUT_API_VERSION
+  data->pressure = NAN;
+  data->size = {NAN, NAN};
+  data->tilt = {NAN, NAN};
+#endif
+
+  return new DevInput::Event(kSbEventTypeInput, data,
+                             &Application::DeleteDestructor<SbInputData>);
+}
+
+DevInput::Event* CreateTouchPadEvent(SbWindow window,
+                                     SbInputEventType type,
+                                     SbKey key,
+                                     SbKeyLocation location,
+                                     int modifiers,
+                                     const SbInputVector& input_vector) {
+  SbInputData* data = new SbInputData();
+  SbMemorySet(data, 0, sizeof(*data));
+
+  data->window = window;
+  data->type = type;
+  data->device_type = kSbInputDeviceTypeTouchPad;
+  data->device_id = kGamepadDeviceId;
+
+  data->key = key;
+  data->key_location = location;
+  data->key_modifiers = modifiers;
+  data->position = input_vector;
+#if SB_API_VERSION >= SB_POINTER_INPUT_API_VERSION
+  data->pressure = NAN;
+  data->size = {NAN, NAN};
+  data->tilt = {NAN, NAN};
+#endif
+
+  return new DevInput::Event(kSbEventTypeInput, data,
+                             &Application::DeleteDestructor<SbInputData>);
+}
+
+}  // namespace
+
+DevInput::Event* DevInputImpl::AxisInputToApplicationEvent(
+    const struct input_event& event,
+    InputDeviceInfo* device_info,
+    int modifiers) {
+  SB_DCHECK(event.type == EV_ABS);
+  SbKey key = kSbKeyUnknown;
+  float axis_value = 0;
+  float previous_axis_value = 0;
+  auto axis_info_it = device_info->axis_info.find(event.code);
+  if (axis_info_it != device_info->axis_info.end()) {
+    struct input_absinfo& axis_info = axis_info_it->second;
+    axis_info.value = event.value;
+    axis_value = GetAxisValue(axis_info);
+    float& stored_axis_value = device_info->axis_value[event.code];
+    previous_axis_value = stored_axis_value;
+    if (previous_axis_value == axis_value) {
+      // If the value is unchanged, don't do anything.
+      return NULL;
+    }
+    stored_axis_value = axis_value;
+  }
+
+  SbKeyLocation location = kSbKeyLocationUnspecified;
+  SbInputVector input_vector;
+  // The mapping for the axis codes can vary from controller to controller.
+  // TODO: Include axis mapping for controllers with different layout.
+  switch (event.code) {
+    case ABS_X:
+      // Report up and left as positive values.
+      input_vector.x = -axis_value;
+      input_vector.y = -device_info->axis_value[ABS_Y];
+      key = kSbKeyGamepadLeftStickLeft;
+      location = kSbKeyLocationLeft;
+      return CreateMoveEventWithKey(window_, key, location, modifiers,
+                                    input_vector);
+    case ABS_Y: {
+      // Report up and left as positive values.
+      input_vector.x = -device_info->axis_value[ABS_X];
+      input_vector.y = -axis_value;
+      key = kSbKeyGamepadLeftStickUp;
+      location = kSbKeyLocationLeft;
+      return CreateMoveEventWithKey(window_, key, location, modifiers,
+                                    input_vector);
+    }
+    case ABS_Z:
+      // Report up and left as positive values.
+      input_vector.x = -axis_value;
+      input_vector.y = -device_info->axis_value[ABS_RZ];
+      key = kSbKeyGamepadRightStickLeft;
+      location = kSbKeyLocationRight;
+      return CreateMoveEventWithKey(window_, key, location, modifiers,
+                                    input_vector);
+    case ABS_RZ:
+      // Report up and left as positive values.
+      input_vector.x = -device_info->axis_value[ABS_Z];
+      input_vector.y = -axis_value;
+      key = kSbKeyGamepadRightStickUp;
+      location = kSbKeyLocationRight;
+      return CreateMoveEventWithKey(window_, key, location, modifiers,
+                                    input_vector);
+    case ABS_RX: {
+      key = kSbKeyGamepadLeftTrigger;
+      location = kSbKeyLocationLeft;
+      // For trigger buttons, the range is [0..1].
+      float trigger_value = (axis_value + 1) / 2;
+      float previous_trigger_value = (previous_axis_value + 1) / 2;
+      return CreateAnalogButtonKeyEvent(window_, trigger_value,
+                                        previous_trigger_value, key, location,
+                                        modifiers, event);
+    }
+    case ABS_RY: {
+      key = kSbKeyGamepadRightTrigger;
+      location = kSbKeyLocationRight;
+      // For trigger buttons, the range is [0..1].
+      float trigger_value = (axis_value + 1) / 2;
+      float previous_trigger_value = (previous_axis_value + 1) / 2;
+      return CreateAnalogButtonKeyEvent(window_, trigger_value,
+                                        previous_trigger_value, key, location,
+                                        modifiers, event);
+    }
+    case ABS_HAT0X: {
+      float axis_value_for_key =
+          std::abs(axis_value) > 0.5f ? axis_value : previous_axis_value;
+      key = (axis_value_for_key < 0) ? kSbKeyGamepadDPadLeft
+                                     : kSbKeyGamepadDPadRight;
+      return CreateAnalogButtonKeyEvent(window_, axis_value,
+                                        previous_axis_value, key, location,
+                                        modifiers, event);
+    }
+    case ABS_HAT0Y: {
+      float axis_value_for_key =
+          std::abs(axis_value) > 0.5f ? axis_value : previous_axis_value;
+      key = (axis_value_for_key < 0) ? kSbKeyGamepadDPadUp
+                                     : kSbKeyGamepadDPadDown;
+      return CreateAnalogButtonKeyEvent(window_, axis_value,
+                                        previous_axis_value, key, location,
+                                        modifiers, event);
+    }
+    case ABS_MT_TRACKING_ID:
+      if (event.value == -1) {
+        bool touchpad_position_is_known = IsTouchpadPositionKnown(device_info);
+        device_info->touchpad_position_state = kTouchPadPositionNone;
+        if (touchpad_position_is_known) {
+          // Touch point is released, report last known position as unpress.
+          input_vector.x = (device_info->axis_value[ABS_MT_POSITION_X] + 1 / 2);
+          input_vector.y = (device_info->axis_value[ABS_MT_POSITION_Y] + 1 / 2);
+          return CreateTouchPadEvent(window_, kSbInputEventTypeUnpress, key,
+                                     location, modifiers, input_vector);
+        }
+      }
+      return NULL;
+    case ABS_MT_POSITION_X: {
+      // If all positions were known before this event, then this event is a
+      // move.
+      SbInputEventType type = IsTouchpadPositionKnown(device_info)
+                                  ? kSbInputEventTypeMove
+                                  : kSbInputEventTypePress;
+      device_info->touchpad_position_state |= kTouchPadPositionX;
+      if (IsTouchpadPositionKnown(device_info)) {
+        // For touchpads, the range is [0..1].
+        input_vector.x = (axis_value + 1) / 2;
+        input_vector.y = (device_info->axis_value[ABS_MT_POSITION_Y] + 1) / 2;
+        return CreateTouchPadEvent(window_, type, key, location, modifiers,
+                                   input_vector);
+      }
+      // Not all axis positions are known yet.
+      return NULL;
+    }
+    case ABS_MT_POSITION_Y: {
+      // If all positions were known before this event, then this event is a
+      // move.
+      SbInputEventType type = IsTouchpadPositionKnown(device_info)
+                                  ? kSbInputEventTypeMove
+                                  : kSbInputEventTypePress;
+      device_info->touchpad_position_state |= kTouchPadPositionY;
+      if (IsTouchpadPositionKnown(device_info)) {
+        // For touchpads, the range is [0..1].
+        input_vector.x = (device_info->axis_value[ABS_MT_POSITION_X] + 1) / 2;
+        input_vector.y = (axis_value + 1) / 2;
+        return CreateTouchPadEvent(window_, type, key, location, modifiers,
+                                   input_vector);
+      }
+      // Not all axis positions are known yet.
+      return NULL;
+    }
+    default:
+      // Ignored event codes.
+      return NULL;
+  }
+
+  SB_NOTREACHED();
+  return NULL;
+}
+
+DevInput::Event* DevInputImpl::KeyInputToApplicationEvent(
+    const struct input_event& event,
+    int modifiers) {
+  SB_DCHECK(event.type == EV_KEY);
   SB_DCHECK(event.value <= 2);
   SbInputData* data = new SbInputData();
   SbMemorySet(data, 0, sizeof(*data));
@@ -715,6 +1134,21 @@
                    &Application::DeleteDestructor<SbInputData>);
 }
 
+DevInput::Event* DevInputImpl::InputToApplicationEvent(
+    const struct input_event& event,
+    InputDeviceInfo* device_info,
+    int modifiers) {
+  // EV_ABS events are axis values: Sticks, dpad, and touchpad.
+  // https://www.kernel.org/doc/Documentation/input/event-codes.txt
+  switch (event.type) {
+    case EV_ABS:
+      return AxisInputToApplicationEvent(event, device_info, modifiers);
+    case EV_KEY:
+      return KeyInputToApplicationEvent(event, modifiers);
+  }
+  return NULL;
+}
+
 }  // namespace
 
 // static
@@ -722,6 +1156,11 @@
   return new DevInputImpl(window);
 }
 
+// static
+DevInput* DevInput::Create(SbWindow window, int wake_up_fd) {
+  return new DevInputImpl(window, wake_up_fd);
+}
+
 }  // namespace dev_input
 }  // namespace shared
 }  // namespace starboard
diff --git a/src/starboard/shared/linux/dev_input/dev_input.h b/src/starboard/shared/linux/dev_input/dev_input.h
index aa8577a..11b94c4 100644
--- a/src/starboard/shared/linux/dev_input/dev_input.h
+++ b/src/starboard/shared/linux/dev_input/dev_input.h
@@ -48,6 +48,11 @@
   // Creates an instance of DevInput for the given window.
   static DevInput* Create(SbWindow window);
 
+  // Creates an instance of DevInput for the given window.
+  // The wake_up_fd will be used in WaitForSystemEventWithTimeout() to return
+  // early when input is available on it.
+  static DevInput* Create(SbWindow window, int wake_up_fd);
+
  protected:
   DevInput() {}
 };
diff --git a/src/starboard/shared/msvc/uwp/toolchain.py b/src/starboard/shared/msvc/uwp/msvc_toolchain.py
similarity index 100%
rename from src/starboard/shared/msvc/uwp/toolchain.py
rename to src/starboard/shared/msvc/uwp/msvc_toolchain.py
diff --git a/src/starboard/shared/starboard/audio_sink/audio_sink_internal.h b/src/starboard/shared/starboard/audio_sink/audio_sink_internal.h
index cf40ad5..4aaeef1 100644
--- a/src/starboard/shared/starboard/audio_sink/audio_sink_internal.h
+++ b/src/starboard/shared/starboard/audio_sink/audio_sink_internal.h
@@ -42,6 +42,9 @@
 #if SB_API_VERSION >= 4
   virtual void SetPlaybackRate(double playback_rate) = 0;
 #endif  // SB_API_VERSION >= 4
+
+  virtual void SetVolume(double volume) = 0;
+
   virtual bool IsType(Type* type) = 0;
 
   // The following two functions will be called during application startup and
diff --git a/src/starboard/shared/starboard/audio_sink/stub_audio_sink_type.cc b/src/starboard/shared/starboard/audio_sink/stub_audio_sink_type.cc
index 597820a..f6ccd09 100644
--- a/src/starboard/shared/starboard/audio_sink/stub_audio_sink_type.cc
+++ b/src/starboard/shared/starboard/audio_sink/stub_audio_sink_type.cc
@@ -44,6 +44,10 @@
   }
 #endif  // SB_API_VERSION >= 4
 
+  void SetVolume(double volume) SB_OVERRIDE {
+    SB_UNREFERENCED_PARAMETER(volume);
+  }
+
  private:
   static void* ThreadEntryPoint(void* context);
   void AudioThreadFunc();
diff --git a/src/starboard/shared/starboard/file_storage/storage_delete_record.cc b/src/starboard/shared/starboard/file_storage/storage_delete_record.cc
index 891fbbb..c5c7fe3 100644
--- a/src/starboard/shared/starboard/file_storage/storage_delete_record.cc
+++ b/src/starboard/shared/starboard/file_storage/storage_delete_record.cc
@@ -18,14 +18,23 @@
 #include "starboard/shared/starboard/file_storage/storage_internal.h"
 #include "starboard/user.h"
 
-bool SbStorageDeleteRecord(SbUser user) {
+bool SbStorageDeleteRecord(SbUser user
+#if SB_API_VERSION >= SB_STORAGE_NAMES_API_VERSION
+                           ,
+                           const char* name
+#endif  // SB_API_VERSION >= SB_STORAGE_NAMES_API_VERSION
+                           ) {
   if (!SbUserIsValid(user)) {
     return false;
   }
 
+#if SB_API_VERSION < SB_STORAGE_NAMES_API_VERSION
+  const char* name = NULL;
+#endif  // SB_API_VERSION < SB_STORAGE_NAMES_API_VERSION
+
   char path[SB_FILE_MAX_PATH];
   bool success = starboard::shared::starboard::GetUserStorageFilePath(
-      user, path, SB_ARRAY_SIZE_INT(path));
+      user, name, path, SB_ARRAY_SIZE_INT(path));
   if (!success) {
     return false;
   }
diff --git a/src/starboard/shared/starboard/file_storage/storage_internal.h b/src/starboard/shared/starboard/file_storage/storage_internal.h
index 6bb3362..a56cd34 100644
--- a/src/starboard/shared/starboard/file_storage/storage_internal.h
+++ b/src/starboard/shared/starboard/file_storage/storage_internal.h
@@ -18,6 +18,8 @@
 #ifndef STARBOARD_SHARED_STARBOARD_FILE_STORAGE_STORAGE_INTERNAL_H_
 #define STARBOARD_SHARED_STARBOARD_FILE_STORAGE_STORAGE_INTERNAL_H_
 
+#include <string>
+
 #include "starboard/file.h"
 #include "starboard/shared/internal_only.h"
 #include "starboard/storage.h"
@@ -27,6 +29,9 @@
 struct SbStorageRecordPrivate {
   SbUser user;
   SbFile file;
+#if SB_API_VERSION >= SB_STORAGE_NAMES_API_VERSION
+  std::string name;
+#endif  // SB_API_VERSION >= SB_STORAGE_NAMES_API_VERSION
 };
 
 namespace starboard {
@@ -34,6 +39,7 @@
 namespace starboard {
 // Gets the path to the storage file for the given user.
 static SB_C_INLINE bool GetUserStorageFilePath(SbUser user,
+                                               const char* name,
                                                char* out_path,
                                                int path_size) {
   bool success = SbUserGetProperty(user, kSbUserPropertyHomeDirectory, out_path,
@@ -42,7 +48,12 @@
     return false;
   }
 
-  SbStringConcat(out_path, "/.starboard.storage", path_size);
+  SbStringConcat(out_path, "/.starboard", path_size);
+  if (name && SbStringGetLength(name) > 0) {
+    SbStringConcat(out_path, ".", path_size);
+    SbStringConcat(out_path, name, path_size);
+  }
+  SbStringConcat(out_path, ".storage", path_size);
   return true;
 }
 }  // namespace starboard
diff --git a/src/starboard/shared/starboard/file_storage/storage_open_record.cc b/src/starboard/shared/starboard/file_storage/storage_open_record.cc
index 5be40f4..aaea393 100644
--- a/src/starboard/shared/starboard/file_storage/storage_open_record.cc
+++ b/src/starboard/shared/starboard/file_storage/storage_open_record.cc
@@ -19,14 +19,23 @@
 #include "starboard/shared/starboard/file_storage/storage_internal.h"
 #include "starboard/user.h"
 
-SbStorageRecord SbStorageOpenRecord(SbUser user) {
+SbStorageRecord SbStorageOpenRecord(SbUser user
+#if SB_API_VERSION >= SB_STORAGE_NAMES_API_VERSION
+                                    ,
+                                    const char* name
+#endif  // SB_API_VERSION >= SB_STORAGE_NAMES_API_VERSION
+                                    ) {
   if (!SbUserIsValid(user)) {
     return kSbStorageInvalidRecord;
   }
 
+#if SB_API_VERSION < SB_STORAGE_NAMES_API_VERSION
+  const char* name = NULL;
+#endif  // SB_API_VERSION < SB_STORAGE_NAMES_API_VERSION
+
   char path[SB_FILE_MAX_PATH];
   bool success = starboard::shared::starboard::GetUserStorageFilePath(
-      user, path, SB_ARRAY_SIZE_INT(path));
+      user, name, path, SB_ARRAY_SIZE_INT(path));
   if (!success) {
     return kSbStorageInvalidRecord;
   }
@@ -43,5 +52,10 @@
   SB_DCHECK(SbStorageIsValidRecord(result));
   result->user = user;
   result->file = file;
+#if SB_API_VERSION >= SB_STORAGE_NAMES_API_VERSION
+  if (name) {
+    result->name = name;
+  }
+#endif
   return result;
 }
diff --git a/src/starboard/shared/starboard/player/filter/audio_renderer_impl_internal.cc b/src/starboard/shared/starboard/player/filter/audio_renderer_impl_internal.cc
index dbaa49e..9e63967 100644
--- a/src/starboard/shared/starboard/player/filter/audio_renderer_impl_internal.cc
+++ b/src/starboard/shared/starboard/player/filter/audio_renderer_impl_internal.cc
@@ -71,6 +71,7 @@
       sink_sample_type_(GetSinkAudioSampleType()),
       bytes_per_frame_(media::GetBytesPerSample(sink_sample_type_) * channels_),
       playback_rate_(1.0),
+      volume_(1.0),
       paused_(true),
       seeking_(false),
       seeking_to_pts_(0),
@@ -178,6 +179,14 @@
 }
 #endif  // SB_API_VERSION >= 4
 
+void AudioRendererImpl::SetVolume(double volume) {
+  SB_DCHECK(BelongsToCurrentThread());
+  volume_ = volume;
+  if (audio_sink_) {
+    audio_sink_->SetVolume(volume_);
+  }
+}
+
 void AudioRendererImpl::Seek(SbMediaTime seek_to_pts) {
   SB_DCHECK(BelongsToCurrentThread());
   SB_DCHECK(seek_to_pts >= 0);
@@ -287,6 +296,7 @@
   // support play/pause.
   audio_sink_->SetPlaybackRate(playback_rate_ > 0.0 ? 1.0 : 0.0);
 #endif  // SB_API_VERSION >= 4
+  audio_sink_->SetVolume(volume_);
 }
 
 void AudioRendererImpl::UpdateSourceStatus(int* frames_in_buffer,
diff --git a/src/starboard/shared/starboard/player/filter/audio_renderer_impl_internal.h b/src/starboard/shared/starboard/player/filter/audio_renderer_impl_internal.h
index d9278da..a469037 100644
--- a/src/starboard/shared/starboard/player/filter/audio_renderer_impl_internal.h
+++ b/src/starboard/shared/starboard/player/filter/audio_renderer_impl_internal.h
@@ -60,6 +60,7 @@
 #if SB_API_VERSION >= 4
   void SetPlaybackRate(double playback_rate) SB_OVERRIDE;
 #endif  // SB_API_VERSION >= 4
+  void SetVolume(double volume) SB_OVERRIDE;
   void Seek(SbMediaTime seek_to_pts) SB_OVERRIDE;
 
   bool IsEndOfStreamWritten() const SB_OVERRIDE {
@@ -117,6 +118,7 @@
   scoped_ptr<AudioResampler> resampler_;
   AudioTimeStretcher time_stretcher_;
   double playback_rate_;
+  double volume_;
 
   atomic_bool paused_;
   atomic_bool seeking_;
diff --git a/src/starboard/shared/starboard/player/filter/audio_renderer_internal.h b/src/starboard/shared/starboard/player/filter/audio_renderer_internal.h
index 1c192c1..a6ca64b 100644
--- a/src/starboard/shared/starboard/player/filter/audio_renderer_internal.h
+++ b/src/starboard/shared/starboard/player/filter/audio_renderer_internal.h
@@ -38,6 +38,7 @@
 #if SB_API_VERSION >= 4
   virtual void SetPlaybackRate(double playback_rate) = 0;
 #endif  // SB_API_VERSION >= 4
+  virtual void SetVolume(double volume) = 0;
   virtual void Seek(SbMediaTime seek_to_pts) = 0;
   virtual bool IsEndOfStreamWritten() const = 0;
   virtual bool IsEndOfStreamPlayed() const = 0;
diff --git a/src/starboard/shared/starboard/player/filter/filter_based_player_worker_handler.cc b/src/starboard/shared/starboard/player/filter/filter_based_player_worker_handler.cc
index 76b6e53..c373d90 100644
--- a/src/starboard/shared/starboard/player/filter/filter_based_player_worker_handler.cc
+++ b/src/starboard/shared/starboard/player/filter/filter_based_player_worker_handler.cc
@@ -62,7 +62,11 @@
       audio_codec_(audio_codec),
       drm_system_(drm_system),
       audio_header_(audio_header),
-      paused_(false)
+      paused_(false),
+#if SB_API_VERSION >= 4
+      playback_rate_(1.0),
+#endif  // SB_API_VERSION >= 4
+      volume_(1.0)
 #if SB_API_VERSION >= 4
       ,
       output_mode_(output_mode),
@@ -87,12 +91,12 @@
   bounds_ = PlayerWorker::Bounds();
 }
 
-bool FilterBasedPlayerWorkerHandler::IsPunchoutMode() const {

-#if SB_API_VERSION >= 4

-  return (output_mode_ == kSbPlayerOutputModePunchOut);

-#else

-  return true;

-#endif  // SB_API_VERSION >= 4

+bool FilterBasedPlayerWorkerHandler::IsPunchoutMode() const {
+#if SB_API_VERSION >= 4
+  return (output_mode_ == kSbPlayerOutputModePunchOut);
+#else
+  return true;
+#endif  // SB_API_VERSION >= 4
 }
 
 bool FilterBasedPlayerWorkerHandler::Init(
@@ -144,6 +148,11 @@
   ::starboard::ScopedLock lock(video_renderer_existence_mutex_);
   media_components->GetRenderers(&audio_renderer_, &video_renderer_);
 
+#if SB_API_VERSION >= 4
+  audio_renderer_->SetPlaybackRate(playback_rate_);
+#endif  // SB_API_VERSION >= 4
+  audio_renderer_->SetVolume(volume_);
+
   job_queue_->Schedule(update_closure_, kUpdateInterval);
 
   return true;
@@ -276,15 +285,26 @@
 bool FilterBasedPlayerWorkerHandler::SetPlaybackRate(double playback_rate) {
   SB_DCHECK(job_queue_->BelongsToCurrentThread());
 
+  playback_rate_ = playback_rate;
+
   if (!audio_renderer_) {
     return false;
   }
 
-  audio_renderer_->SetPlaybackRate(playback_rate);
+  audio_renderer_->SetPlaybackRate(playback_rate_);
   return true;
 }
 #endif  // SB_API_VERSION >= 4
 
+void FilterBasedPlayerWorkerHandler::SetVolume(double volume) {
+  SB_DCHECK(job_queue_->BelongsToCurrentThread());
+
+  volume_ = volume;
+  if (audio_renderer_) {
+    audio_renderer_->SetVolume(volume_);
+  }
+}
+
 bool FilterBasedPlayerWorkerHandler::SetBounds(
     const PlayerWorker::Bounds& bounds) {
   SB_DCHECK(job_queue_->BelongsToCurrentThread());
@@ -328,9 +348,9 @@
     player_worker_->UpdateDroppedVideoFrames(
         video_renderer_->GetDroppedFrames());
 
-   if (IsPunchoutMode()) {

-      shared::starboard::Application::Get()->HandleFrame(

-          player_, frame, bounds_.x, bounds_.y, bounds_.width, bounds_.height);

+    if (IsPunchoutMode()) {
+      shared::starboard::Application::Get()->HandleFrame(
+          player_, frame, bounds_.x, bounds_.y, bounds_.width, bounds_.height);
     }
 
     (*player_worker_.*update_media_time_cb_)(audio_renderer_->GetCurrentTime());
@@ -355,10 +375,10 @@
   }
   video_renderer.reset();
 
-  if (IsPunchoutMode()) {

-    // Clear the video frame as we terminate.

-    shared::starboard::Application::Get()->HandleFrame(

-        player_, VideoFrame::CreateEOSFrame(), 0, 0, 0, 0);

+  if (IsPunchoutMode()) {
+    // Clear the video frame as we terminate.
+    shared::starboard::Application::Get()->HandleFrame(
+        player_, VideoFrame::CreateEOSFrame(), 0, 0, 0, 0);
   }
 }
 
diff --git a/src/starboard/shared/starboard/player/filter/filter_based_player_worker_handler.h b/src/starboard/shared/starboard/player/filter/filter_based_player_worker_handler.h
index 8715ab9..20655ca 100644
--- a/src/starboard/shared/starboard/player/filter/filter_based_player_worker_handler.h
+++ b/src/starboard/shared/starboard/player/filter/filter_based_player_worker_handler.h
@@ -68,6 +68,7 @@
 #if SB_API_VERSION >= 4
   bool SetPlaybackRate(double playback_rate) SB_OVERRIDE;
 #endif  // SB_API_VERSION >= 4
+  void SetVolume(double volume) SB_OVERRIDE;
   bool SetBounds(const PlayerWorker::Bounds& bounds) SB_OVERRIDE;
   void Stop() SB_OVERRIDE;
 
@@ -98,6 +99,10 @@
   scoped_ptr<VideoRenderer> video_renderer_;
 
   bool paused_;
+#if SB_API_VERSION >= 4
+  double playback_rate_;
+#endif  // SB_API_VERSION >= 4
+  double volume_;
   PlayerWorker::Bounds bounds_;
   Closure update_closure_;
 
diff --git a/src/starboard/shared/starboard/player/player_internal.cc b/src/starboard/shared/starboard/player/player_internal.cc
index 20268ea..3b46a5d 100644
--- a/src/starboard/shared/starboard/player/player_internal.cc
+++ b/src/starboard/shared/starboard/player/player_internal.cc
@@ -133,8 +133,8 @@
 #endif  // SB_API_VERSION >= 4
 
 void SbPlayerPrivate::SetVolume(double volume) {
-  SB_UNREFERENCED_PARAMETER(volume);
-  SB_NOTIMPLEMENTED();
+  volume_ = volume;
+  worker_->SetVolume(volume_);
 }
 
 void SbPlayerPrivate::UpdateMediaTime(SbMediaTime media_time, int ticket) {
diff --git a/src/starboard/shared/starboard/player/player_worker.cc b/src/starboard/shared/starboard/player/player_worker.cc
index 38a0a25..24e834c 100644
--- a/src/starboard/shared/starboard/player/player_worker.cc
+++ b/src/starboard/shared/starboard/player/player_worker.cc
@@ -270,6 +270,11 @@
 }
 #endif  // SB_API_VERSION >= 4
 
+void PlayerWorker::DoSetVolume(double volume) {
+  SB_DCHECK(job_queue_->BelongsToCurrentThread());
+  handler_->SetVolume(volume);
+}
+
 void PlayerWorker::DoStop() {
   SB_DCHECK(job_queue_->BelongsToCurrentThread());
 
diff --git a/src/starboard/shared/starboard/player/player_worker.h b/src/starboard/shared/starboard/player/player_worker.h
index a1d9333..84be839 100644
--- a/src/starboard/shared/starboard/player/player_worker.h
+++ b/src/starboard/shared/starboard/player/player_worker.h
@@ -83,6 +83,8 @@
 #if SB_API_VERSION >= 4
     virtual bool SetPlaybackRate(double playback_rate) = 0;
 #endif  // SB_API_VERSION >= 4
+    virtual void SetVolume(double volume) = 0;
+
     virtual bool SetBounds(const Bounds& bounds) = 0;
 
     // Once this function returns, all processing on the Handler and related
@@ -147,6 +149,10 @@
   }
 #endif  // SB_API_VERSION >= 4
 
+  void SetVolume(double volume) {
+    job_queue_->Schedule(Bind(&PlayerWorker::DoSetVolume, this, volume));
+  }
+
   void UpdateDroppedVideoFrames(int dropped_video_frames) {
     host_->UpdateDroppedVideoFrames(dropped_video_frames);
   }
@@ -177,6 +183,7 @@
 #if SB_API_VERSION >= 4
   void DoSetPlaybackRate(double rate);
 #endif  // SB_API_VERSION >= 4
+  void DoSetVolume(double volume);
   void DoStop();
 
   void UpdateDecoderState(SbMediaType type, SbPlayerDecoderState state);
diff --git a/src/starboard/shared/stub/drm_create_system.cc b/src/starboard/shared/stub/drm_create_system.cc
index a9a1c84..64139a9 100644
--- a/src/starboard/shared/stub/drm_create_system.cc
+++ b/src/starboard/shared/stub/drm_create_system.cc
@@ -14,6 +14,24 @@
 
 #include "starboard/drm.h"
 
+#if SB_API_VERSION >= SB_DRM_KEY_STATUSES_UPDATE_SUPPORT_API_VERSION
+
+SbDrmSystem SbDrmCreateSystem(
+    const char* key_system,
+    void* context,
+    SbDrmSessionUpdateRequestFunc update_request_callback,
+    SbDrmSessionUpdatedFunc session_updated_callback,
+    SbDrmSessionKeyStatusesChangedFunc key_statuses_changed_callback) {
+  SB_UNREFERENCED_PARAMETER(context);
+  SB_UNREFERENCED_PARAMETER(key_system);
+  SB_UNREFERENCED_PARAMETER(update_request_callback);
+  SB_UNREFERENCED_PARAMETER(session_updated_callback);
+  SB_UNREFERENCED_PARAMETER(key_statuses_changed_callback);
+  return kSbDrmSystemInvalid;
+}
+
+#else  // SB_API_VERSION >= SB_DRM_KEY_STATUSES_UPDATE_SUPPORT_API_VERSION
+
 SbDrmSystem SbDrmCreateSystem(
     const char* key_system,
     void* context,
@@ -25,3 +43,5 @@
   SB_UNREFERENCED_PARAMETER(session_updated_callback);
   return kSbDrmSystemInvalid;
 }
+
+#endif  // SB_API_VERSION >= SB_DRM_KEY_STATUSES_UPDATE_SUPPORT_API_VERSION
diff --git a/src/starboard/shared/stub/storage_delete_record.cc b/src/starboard/shared/stub/storage_delete_record.cc
index ba13cd1..a669054 100644
--- a/src/starboard/shared/stub/storage_delete_record.cc
+++ b/src/starboard/shared/stub/storage_delete_record.cc
@@ -14,6 +14,11 @@
 
 #include "starboard/storage.h"
 
-bool SbStorageDeleteRecord(SbUser /*user*/) {
+bool SbStorageDeleteRecord(SbUser /*user*/
+#if SB_API_VERSION >= SB_STORAGE_NAMES_API_VERSION
+                           ,
+                           const char* /*name*/
+#endif  // SB_API_VERSION >= SB_STORAGE_NAMES_API_VERSION
+                           ) {
   return false;
 }
diff --git a/src/starboard/shared/stub/storage_open_record.cc b/src/starboard/shared/stub/storage_open_record.cc
index bcd5f3d..b0f503e 100644
--- a/src/starboard/shared/stub/storage_open_record.cc
+++ b/src/starboard/shared/stub/storage_open_record.cc
@@ -14,6 +14,11 @@
 
 #include "starboard/storage.h"
 
-SbStorageRecord SbStorageOpenRecord(SbUser /*user*/) {
+SbStorageRecord SbStorageOpenRecord(SbUser /*user*/
+#if SB_API_VERSION >= SB_STORAGE_NAMES_API_VERSION
+                                    ,
+                                    const char* /*name*/
+#endif  // SB_API_VERSION >= SB_STORAGE_NAMES_API_VERSION
+                                    ) {
   return kSbStorageInvalidRecord;
 }
diff --git a/src/starboard/shared/test_webapi_extension/my_new_enum.idl b/src/starboard/shared/test_webapi_extension/my_new_enum.idl
new file mode 100644
index 0000000..1a6f73c
--- /dev/null
+++ b/src/starboard/shared/test_webapi_extension/my_new_enum.idl
@@ -0,0 +1,19 @@
+// Copyright 2017 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+enum MyNewEnum {
+  "apples",
+  "oranges",
+  "peaches"
+};
diff --git a/src/starboard/shared/test_webapi_extension/my_new_interface.cc b/src/starboard/shared/test_webapi_extension/my_new_interface.cc
new file mode 100644
index 0000000..18af5b4
--- /dev/null
+++ b/src/starboard/shared/test_webapi_extension/my_new_interface.cc
@@ -0,0 +1,29 @@
+// Copyright 2017 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "starboard/shared/test_webapi_extension/my_new_interface.h"
+
+namespace cobalt {
+namespace webapi_extension {
+
+MyNewInterface::MyNewInterface(const scoped_refptr<dom::Window>& window) {
+  UNREFERENCED_PARAMETER(window);
+  // Provide an initial value for the enum.
+  enum_value_ = kMyNewEnumApples;
+}
+
+MyNewInterface::~MyNewInterface() OVERRIDE {}
+
+}  // namespace webapi_extension
+}  // namespace cobalt
diff --git a/src/starboard/shared/test_webapi_extension/my_new_interface.h b/src/starboard/shared/test_webapi_extension/my_new_interface.h
new file mode 100644
index 0000000..eb30d4e
--- /dev/null
+++ b/src/starboard/shared/test_webapi_extension/my_new_interface.h
@@ -0,0 +1,55 @@
+// Copyright 2017 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef STARBOARD_SHARED_TEST_WEBAPI_EXTENSION_MY_NEW_INTERFACE_H_
+#define STARBOARD_SHARED_TEST_WEBAPI_EXTENSION_MY_NEW_INTERFACE_H_
+
+#include <string>
+
+#include "cobalt/dom/window.h"
+#include "cobalt/script/wrappable.h"
+#include "starboard/shared/test_webapi_extension/my_new_enum.h"
+
+namespace cobalt {
+namespace webapi_extension {
+
+class MyNewInterface : public script::Wrappable {
+ public:
+  explicit MyNewInterface(const scoped_refptr<dom::Window>& window);
+
+  const std::string& foo() const { return foo_; }
+  void set_foo(const std::string& value) { foo_ = value; }
+
+  void SetMyNewEnum(MyNewEnum value) { enum_value_ = value; }
+  MyNewEnum GetMyNewEnum() const { return enum_value_; }
+
+  // All types derived from script::Wrappable must have this annotation.
+  DEFINE_WRAPPABLE_TYPE(MyNewInterface);
+
+ private:
+  // Since script::Wrappable inherits from base::RefCounted<>, we make the
+  // destructor private.
+  ~MyNewInterface() OVERRIDE;
+
+  std::string foo_;
+
+  MyNewEnum enum_value_;
+
+  DISALLOW_COPY_AND_ASSIGN(MyNewInterface);
+};
+
+}  // namespace webapi_extension
+}  // namespace cobalt
+
+#endif  // STARBOARD_SHARED_TEST_WEBAPI_EXTENSION_MY_NEW_INTERFACE_H_
diff --git a/src/starboard/shared/test_webapi_extension/my_new_interface.idl b/src/starboard/shared/test_webapi_extension/my_new_interface.idl
new file mode 100644
index 0000000..3e64764
--- /dev/null
+++ b/src/starboard/shared/test_webapi_extension/my_new_interface.idl
@@ -0,0 +1,20 @@
+// Copyright 2017 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+interface MyNewInterface {
+  attribute DOMString foo;
+
+  void SetMyNewEnum(MyNewEnum value);
+  MyNewEnum GetMyNewEnum();
+};
diff --git a/src/starboard/shared/test_webapi_extension/test_webapi_extension.gypi b/src/starboard/shared/test_webapi_extension/test_webapi_extension.gypi
new file mode 100644
index 0000000..74de31a
--- /dev/null
+++ b/src/starboard/shared/test_webapi_extension/test_webapi_extension.gypi
@@ -0,0 +1,29 @@
+# Copyright 2017 Google Inc. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# This file can be included from a platform's gyp_configuration.gypi file to
+# setup the test custom webapi_extension defined in this directory to be
+# injected into that platform.
+{
+  'variables': {
+    'cobalt_webapi_extension_source_idl_files': [
+      'my_new_interface.idl'
+    ],
+    'cobalt_webapi_extension_generated_header_idl_files': [
+      'my_new_enum.idl'
+    ],
+    'cobalt_webapi_extension_gyp_target':
+      '<(DEPTH)/starboard/shared/test_webapi_extension/webapi_extension.gyp:cobalt_test_webapi_extension',
+  },
+}
\ No newline at end of file
diff --git a/src/starboard/shared/test_webapi_extension/webapi_extension.cc b/src/starboard/shared/test_webapi_extension/webapi_extension.cc
new file mode 100644
index 0000000..06bb89c
--- /dev/null
+++ b/src/starboard/shared/test_webapi_extension/webapi_extension.cc
@@ -0,0 +1,38 @@
+// Copyright 2017 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "cobalt/browser/webapi_extension.h"
+
+#include "base/compiler_specific.h"
+#include "cobalt/script/global_environment.h"
+#include "starboard/shared/test_webapi_extension/my_new_interface.h"
+
+namespace cobalt {
+namespace browser {
+
+base::optional<std::string> GetWebAPIExtensionObjectPropertyName() {
+  return std::string("myInterface");
+}
+
+scoped_refptr<script::Wrappable> CreateWebAPIExtensionObject(
+    const scoped_refptr<dom::Window>& window,
+    script::GlobalEnvironment* global_environment) {
+  UNREFERENCED_PARAMETER(global_environment);
+
+  return scoped_refptr<script::Wrappable>(
+      new webapi_extension::MyNewInterface(window));
+}
+
+}  // namespace browser
+}  // namespace cobalt
diff --git a/src/starboard/shared/test_webapi_extension/webapi_extension.gyp b/src/starboard/shared/test_webapi_extension/webapi_extension.gyp
new file mode 100644
index 0000000..5c238e0
--- /dev/null
+++ b/src/starboard/shared/test_webapi_extension/webapi_extension.gyp
@@ -0,0 +1,35 @@
+# Copyright 2017 Google Inc. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+{
+  'targets': [
+    {
+      'target_name': 'cobalt_test_webapi_extension',
+      'type': 'static_library',
+
+      # List of all source files and header files needed to support the IDL
+      # definitions.
+      'sources': [
+        'my_new_interface.h',
+        'my_new_interface.cc',
+        'webapi_extension.cc',
+      ],
+
+      'dependencies': [
+        '<(DEPTH)/cobalt/dom/dom.gyp:dom',
+        '<(DEPTH)/cobalt/script/script.gyp:script',
+        '<(DEPTH)/base/base.gyp:base',
+      ],
+    },
+  ],
+}
diff --git a/src/starboard/shared/uwp/cobalt/xhr_modify_headers.cc b/src/starboard/shared/uwp/cobalt/xhr_modify_headers.cc
index eaf8bd3..6f71b80 100644
--- a/src/starboard/shared/uwp/cobalt/xhr_modify_headers.cc
+++ b/src/starboard/shared/uwp/cobalt/xhr_modify_headers.cc
@@ -98,7 +98,7 @@
 
 WebTokenRequestResult^ RequestToken(WebTokenRequest^ request) {
   using starboard::shared::uwp::WaitForResult;
-  IAsyncOperation<WebTokenRequestResult ^> ^ request_operation = nullptr;
+  IAsyncOperation<WebTokenRequestResult^>^ request_operation = nullptr;
   base::WaitableEvent request_operation_set(false, false);
   // Ensure WebAuthenticationCoreManager::RequestTokenAsync is called on the
   // UI thread, since documentation states that "This method cannot be called
diff --git a/src/starboard/shared/uwp/starboard_platform.gypi b/src/starboard/shared/uwp/starboard_platform.gypi
index ee1b5bd..85f0f72 100644
--- a/src/starboard/shared/uwp/starboard_platform.gypi
+++ b/src/starboard/shared/uwp/starboard_platform.gypi
@@ -22,6 +22,8 @@
       'system_clear_platform_error.cc',
       'system_get_device_type.cc',
       'system_get_property.cc',
+      'system_get_total_cpu_memory.cc',
+      'system_get_used_cpu_memory.cc',
       'system_raise_platform_error.cc',
       'window_create.cc',
       'window_destroy.cc',
diff --git a/src/starboard/shared/uwp/system_get_device_type.cc b/src/starboard/shared/uwp/system_get_device_type.cc
index 81179ca..33ce323 100644
--- a/src/starboard/shared/uwp/system_get_device_type.cc
+++ b/src/starboard/shared/uwp/system_get_device_type.cc
@@ -23,7 +23,7 @@
 using Windows::System::Profile::AnalyticsVersionInfo;
 
 SbSystemDeviceType SbSystemGetDeviceType() {
-  AnalyticsVersionInfo ^ version_info = AnalyticsInfo::VersionInfo;
+  AnalyticsVersionInfo^ version_info = AnalyticsInfo::VersionInfo;
   std::string family = starboard::shared::win32::platformStringToString(
       version_info->DeviceFamily);
 
diff --git a/src/starboard/shared/uwp/system_get_total_cpu_memory.cc b/src/starboard/shared/uwp/system_get_total_cpu_memory.cc
new file mode 100644
index 0000000..293fd7d
--- /dev/null
+++ b/src/starboard/shared/uwp/system_get_total_cpu_memory.cc
@@ -0,0 +1,21 @@
+// Copyright 2017 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "starboard/system.h"
+
+using Windows::System::MemoryManager;
+
+int64_t SbSystemGetTotalCPUMemory() {
+  return static_cast<int64_t>(MemoryManager::AppMemoryUsageLimit);
+}
diff --git a/src/starboard/shared/uwp/system_get_used_cpu_memory.cc b/src/starboard/shared/uwp/system_get_used_cpu_memory.cc
new file mode 100644
index 0000000..adc9c0d
--- /dev/null
+++ b/src/starboard/shared/uwp/system_get_used_cpu_memory.cc
@@ -0,0 +1,21 @@
+// Copyright 2017 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "starboard/system.h"
+
+using Windows::System::MemoryManager;
+
+int64_t SbSystemGetUsedCPUMemory() {
+  return static_cast<int64_t>(MemoryManager::AppMemoryUsage);
+}
diff --git a/src/starboard/shared/win32/audio_decoder.cc b/src/starboard/shared/win32/audio_decoder.cc
index d6749f2..879202c 100644
--- a/src/starboard/shared/win32/audio_decoder.cc
+++ b/src/starboard/shared/win32/audio_decoder.cc
@@ -56,19 +56,24 @@
 };
 
 AudioDecoder::AudioDecoder(SbMediaAudioCodec audio_codec,
-                           const SbMediaAudioHeader& audio_header)
-    : sample_type_(kSbMediaAudioSampleTypeFloat32),
-      stream_ended_(false),
-      audio_codec_(audio_codec),
-      audio_header_(audio_header) {
+                           const SbMediaAudioHeader& audio_header,
+                           SbDrmSystem drm_system)
+    : audio_codec_(audio_codec),
+      audio_header_(audio_header),
+      drm_system_(drm_system),
+      sample_type_(kSbMediaAudioSampleTypeFloat32),
+      stream_ended_(false) {
   SB_DCHECK(audio_codec == kSbMediaAudioCodecAac);
   decoder_impl_ = AbstractWin32AudioDecoder::Create(
-      audio_codec_, GetStorageType(), GetSampleType(), audio_header_);
+      audio_codec_, GetStorageType(), GetSampleType(), audio_header_,
+      drm_system_);
   decoder_thread_.reset(new AudioDecoderThread(decoder_impl_.get(), this));
   callback_scheduler_.reset(new CallbackScheduler());
 }
 
 AudioDecoder::~AudioDecoder() {
+  SB_DCHECK(thread_checker_.CalledOnValidThread());
+
   decoder_thread_.reset(nullptr);
   decoder_impl_.reset(nullptr);
   callback_scheduler_.reset(nullptr);
@@ -76,7 +81,9 @@
 
 void AudioDecoder::Decode(const scoped_refptr<InputBuffer>& input_buffer,
                           const Closure& consumed_cb) {
+  SB_DCHECK(thread_checker_.CalledOnValidThread());
   SB_DCHECK(input_buffer);
+
   callback_scheduler_->SetCallbackOnce(consumed_cb);
   callback_scheduler_->OnCallbackSignaled();
   const bool can_take_more_data = decoder_thread_->QueueInput(input_buffer);
@@ -91,22 +98,29 @@
 }
 
 void AudioDecoder::WriteEndOfStream() {
+  SB_DCHECK(thread_checker_.CalledOnValidThread());
+
   ::starboard::ScopedLock lock(mutex_);
   stream_ended_ = true;
   decoder_thread_->QueueEndOfStream();
 }
 
 scoped_refptr<AudioDecoder::DecodedAudio> AudioDecoder::Read() {
+  SB_DCHECK(thread_checker_.CalledOnValidThread());
+
   DecodedAudioPtr data = decoded_data_.PopFront();
   SB_DCHECK(data);
   return data;
 }
 
 void AudioDecoder::Reset() {
+  SB_DCHECK(thread_checker_.CalledOnValidThread());
+
   decoder_thread_.reset(nullptr);
   decoder_impl_.reset(nullptr);
   decoder_impl_ = AbstractWin32AudioDecoder::Create(
-      audio_codec_, GetStorageType(), GetSampleType(), audio_header_);
+      audio_codec_, GetStorageType(), GetSampleType(), audio_header_,
+      drm_system_);
   decoder_thread_.reset(new AudioDecoderThread(decoder_impl_.get(), this));
   decoded_data_.Clear();
   stream_ended_ = false;
@@ -114,14 +128,20 @@
 }
 
 SbMediaAudioSampleType AudioDecoder::GetSampleType() const {
+  SB_DCHECK(thread_checker_.CalledOnValidThread());
+
   return sample_type_;
 }
 
 int AudioDecoder::GetSamplesPerSecond() const {
+  SB_DCHECK(thread_checker_.CalledOnValidThread());
+
   return audio_header_.samples_per_second;
 }
 
 void AudioDecoder::Initialize(const Closure& output_cb) {
+  SB_DCHECK(thread_checker_.CalledOnValidThread());
+
   SB_DCHECK(output_cb.is_valid());
   SB_DCHECK(!output_cb_.is_valid());
   output_cb_ = output_cb;
diff --git a/src/starboard/shared/win32/audio_decoder.h b/src/starboard/shared/win32/audio_decoder.h
index f036287..5bd335c 100644
--- a/src/starboard/shared/win32/audio_decoder.h
+++ b/src/starboard/shared/win32/audio_decoder.h
@@ -18,11 +18,13 @@
 #include "starboard/common/ref_counted.h"
 #include "starboard/common/scoped_ptr.h"
 #include "starboard/configuration.h"
+#include "starboard/drm.h"
 #include "starboard/media.h"
 #include "starboard/shared/internal_only.h"
 #include "starboard/shared/starboard/player/decoded_audio_internal.h"
 #include "starboard/shared/starboard/player/filter/audio_decoder_internal.h"
 #include "starboard/shared/starboard/player/job_queue.h"
+#include "starboard/shared/starboard/thread_checker.h"
 #include "starboard/shared/win32/atomic_queue.h"
 #include "starboard/shared/win32/audio_decoder_thread.h"
 #include "starboard/shared/win32/media_common.h"
@@ -32,16 +34,14 @@
 namespace shared {
 namespace win32 {
 
-using JobQueue = ::starboard::shared::starboard::player::JobQueue;
-using JobOwner = JobQueue::JobOwner;
-
 class AudioDecoder
     : public ::starboard::shared::starboard::player::filter::AudioDecoder,
-      private JobOwner,
+      private ::starboard::shared::starboard::player::JobQueue::JobOwner,
       private AudioDecodedCallback {
  public:
   AudioDecoder(SbMediaAudioCodec audio_codec,
-               const SbMediaAudioHeader& audio_header);
+               const SbMediaAudioHeader& audio_header,
+               SbDrmSystem drm_system);
   ~AudioDecoder() SB_OVERRIDE;
 
   void Decode(const scoped_refptr<InputBuffer>& input_buffer,
@@ -60,9 +60,13 @@
 
  private:
   class CallbackScheduler;
-  SbMediaAudioHeader audio_header_;
-  SbMediaAudioSampleType sample_type_;
+
+  ::starboard::shared::starboard::ThreadChecker thread_checker_;
+
   SbMediaAudioCodec audio_codec_;
+  SbMediaAudioHeader audio_header_;
+  SbDrmSystem drm_system_;
+  SbMediaAudioSampleType sample_type_;
   bool stream_ended_;
 
   AtomicQueue<DecodedAudioPtr> decoded_data_;
@@ -71,7 +75,7 @@
   scoped_ptr<AudioDecoderThread> decoder_thread_;
   Closure output_cb_;
 
-  ::starboard::Mutex mutex_;
+  Mutex mutex_;
 };
 
 }  // namespace win32
diff --git a/src/starboard/shared/win32/audio_sink.cc b/src/starboard/shared/win32/audio_sink.cc
index c5d8de2..5967211 100644
--- a/src/starboard/shared/win32/audio_sink.cc
+++ b/src/starboard/shared/win32/audio_sink.cc
@@ -72,6 +72,10 @@
     ScopedLock lock(mutex_);
     playback_rate_ = playback_rate;
   }
+  void SetVolume(double volume) SB_OVERRIDE {
+    ScopedLock lock(mutex_);
+    volume_ = volume;
+  }
 
  private:
   static void* ThreadEntryPoint(void* context);
@@ -99,6 +103,7 @@
   ::starboard::Mutex mutex_;
   bool destroying_;
   double playback_rate_;
+  double volume_;
 };
 
 XAudioAudioSink::XAudioAudioSink(
@@ -118,7 +123,8 @@
       frame_buffers_size_in_frames_(frame_buffers_size_in_frames),
       wfx_(wfx),
       destroying_(false),
-      playback_rate_(1.0) {
+      playback_rate_(1.0),
+      volume_(1.0) {
   // TODO: Check MaxFrequencyRatio
   CHECK_HRESULT_OK(
       type_->x_audio2_->CreateSourceVoice(&source_voice_, &wfx, 0,
@@ -187,6 +193,7 @@
     int frames_in_buffer, offset_in_frames;
     bool is_playing, is_eos_reached;
     bool is_playback_rate_zero;
+    // TODO: Support |volume_| here as well...
     {
       ScopedLock lock(mutex_);
       is_playback_rate_zero = playback_rate_ == 0.0;
diff --git a/src/starboard/shared/win32/decode_target_internal.cc b/src/starboard/shared/win32/decode_target_internal.cc
index 70b8e32..8188621 100644
--- a/src/starboard/shared/win32/decode_target_internal.cc
+++ b/src/starboard/shared/win32/decode_target_internal.cc
@@ -26,6 +26,7 @@
 #include "third_party/angle/include/EGL/egl.h"
 #include "third_party/angle/include/EGL/eglext.h"
 #include "third_party/angle/include/GLES2/gl2.h"
+#include "third_party/angle/include/GLES2/gl2ext.h"
 
 using Microsoft::WRL::ComPtr;
 using starboard::shared::win32::VideoFramePtr;
@@ -73,24 +74,24 @@
   SbDecodeTargetInfoPlane* planeY = &(info.planes[kSbDecodeTargetPlaneY]);
   SbDecodeTargetInfoPlane* planeUV = &(info.planes[kSbDecodeTargetPlaneUV]);
 
-  planeY->width = texture_desc.Width;
-  planeY->height = texture_desc.Height;
+  planeY->width = info.width;
+  planeY->height = info.height;
   planeY->content_region.left = 0;
-  planeY->content_region.top = 0;
-  planeY->content_region.right = texture_desc.Width;
-  planeY->content_region.bottom = texture_desc.Height;
+  planeY->content_region.top = info.height;
+  planeY->content_region.right = frame->width();
+  planeY->content_region.bottom = info.height - frame->height();
 
-  planeUV->width = texture_desc.Width / 2;
-  planeUV->height = texture_desc.Height / 2;
-  planeUV->content_region.left = 0;
-  planeUV->content_region.top = 0;
-  planeUV->content_region.right = texture_desc.Width / 2;
-  planeUV->content_region.bottom = texture_desc.Height / 2;
+  planeUV->width = info.width / 2;
+  planeUV->height = info.height / 2;
+  planeUV->content_region.left = planeY->content_region.left / 2;
+  planeUV->content_region.top = planeY->content_region.top / 2;
+  planeUV->content_region.right = planeY->content_region.right / 2;
+  planeUV->content_region.bottom = planeY->content_region.bottom / 2;
 
   EGLint luma_texture_attributes[] = {EGL_WIDTH,
-                                      static_cast<EGLint>(texture_desc.Width),
+                                      static_cast<EGLint>(info.width),
                                       EGL_HEIGHT,
-                                      static_cast<EGLint>(texture_desc.Height),
+                                      static_cast<EGLint>(info.height),
                                       EGL_TEXTURE_TARGET,
                                       EGL_TEXTURE_2D,
                                       EGL_TEXTURE_FORMAT,
@@ -119,16 +120,16 @@
                                  dxgi_buffer.GetAddressOf());
   SB_DCHECK(SUCCEEDED(hr));
 
-  EGLSurface surface = eglCreatePbufferFromClientBuffer(
-      display, EGL_D3D_TEXTURE_ANGLE, d3texture.Get(), config,
-      luma_texture_attributes);
+  surface[0] = eglCreatePbufferFromClientBuffer(display, EGL_D3D_TEXTURE_ANGLE,
+                                                d3texture.Get(), config,
+                                                luma_texture_attributes);
 
-  SB_DCHECK(surface != EGL_NO_SURFACE);
+  SB_DCHECK(surface[0] != EGL_NO_SURFACE);
 
   glBindTexture(GL_TEXTURE_2D, gl_textures[0]);
   SB_DCHECK(glGetError() == GL_NO_ERROR);
 
-  ok = eglBindTexImage(display, surface, EGL_BACK_BUFFER);
+  ok = eglBindTexImage(display, surface[0], EGL_BACK_BUFFER);
   SB_DCHECK(ok);
 
   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
@@ -136,6 +137,7 @@
 
   planeY->texture = gl_textures[0];
   planeY->gl_texture_target = GL_TEXTURE_2D;
+  planeY->gl_texture_format = GL_RED_EXT;
 
   // This tells ANGLE that the texture it creates should draw
   // the chroma channel on R8G8.
@@ -145,24 +147,24 @@
 
   EGLint chroma_texture_attributes[] = {
       EGL_WIDTH,
-      static_cast<EGLint>(texture_desc.Width) / 2,
+      static_cast<EGLint>(info.width) / 2,
       EGL_HEIGHT,
-      static_cast<EGLint>(texture_desc.Height) / 2,
+      static_cast<EGLint>(info.height) / 2,
       EGL_TEXTURE_TARGET,
       EGL_TEXTURE_2D,
       EGL_TEXTURE_FORMAT,
       EGL_TEXTURE_RGBA,
       EGL_NONE};
-  surface = eglCreatePbufferFromClientBuffer(display, EGL_D3D_TEXTURE_ANGLE,
-                                             d3texture.Get(), config,
-                                             chroma_texture_attributes);
+  surface[1] = eglCreatePbufferFromClientBuffer(display, EGL_D3D_TEXTURE_ANGLE,
+                                                d3texture.Get(), config,
+                                                chroma_texture_attributes);
 
-  SB_DCHECK(surface != EGL_NO_SURFACE);
+  SB_DCHECK(surface[1] != EGL_NO_SURFACE);
 
   glBindTexture(GL_TEXTURE_2D, gl_textures[1]);
   SB_DCHECK(glGetError() == GL_NO_ERROR);
 
-  ok = eglBindTexImage(display, surface, EGL_BACK_BUFFER);
+  ok = eglBindTexImage(display, surface[1], EGL_BACK_BUFFER);
   SB_DCHECK(ok);
 
   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
@@ -170,6 +172,7 @@
 
   planeUV->texture = gl_textures[1];
   planeUV->gl_texture_target = GL_TEXTURE_2D;
+  planeUV->gl_texture_format = GL_RG_EXT;
 
   hr = d3texture->SetPrivateData(kCobaltDxgiBuffer, 0, nullptr);
   SB_DCHECK(SUCCEEDED(hr));
@@ -178,6 +181,14 @@
 SbDecodeTargetPrivate::~SbDecodeTargetPrivate() {
   glDeleteTextures(1, &(info.planes[kSbDecodeTargetPlaneY].texture));
   glDeleteTextures(1, &(info.planes[kSbDecodeTargetPlaneUV].texture));
+
+  EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
+
+  eglReleaseTexImage(display, surface[0], EGL_BACK_BUFFER);
+  eglDestroySurface(display, surface[0]);
+
+  eglReleaseTexImage(display, surface[1], EGL_BACK_BUFFER);
+  eglDestroySurface(display, surface[1]);
 }
 
 void SbDecodeTargetRelease(SbDecodeTarget decode_target) {
diff --git a/src/starboard/shared/win32/decode_target_internal.h b/src/starboard/shared/win32/decode_target_internal.h
index 6204200..81aa66e 100644
--- a/src/starboard/shared/win32/decode_target_internal.h
+++ b/src/starboard/shared/win32/decode_target_internal.h
@@ -23,6 +23,9 @@
   // Publicly accessible information about the decode target.
   SbDecodeTargetInfo info;
   ::starboard::shared::win32::VideoFramePtr frame;
+  // EGLSurface is defined as void* in "third_party/angle/include/EGL/egl.h".
+  // Use void* directly here to avoid `egl.h` being included broadly.
+  void* surface[2];
   explicit SbDecodeTargetPrivate(starboard::shared::win32::VideoFramePtr frame);
   ~SbDecodeTargetPrivate();
 };
diff --git a/src/starboard/shared/win32/gyp_configuration.py b/src/starboard/shared/win32/gyp_configuration.py
index 0bf0e26..93f92f1 100644
--- a/src/starboard/shared/win32/gyp_configuration.py
+++ b/src/starboard/shared/win32/gyp_configuration.py
@@ -85,5 +85,5 @@
   def GetToolchain(self):
     sys.path.append(
         os.path.join(STARBOARD_ROOT, 'shared', 'msvc', 'uwp'))
-    from toolchain import MSVCUWPToolchain  # pylint: disable=g-import-not-at-top,g-bad-import-order
+    from msvc_toolchain import MSVCUWPToolchain  # pylint: disable=g-import-not-at-top,g-bad-import-order
     return MSVCUWPToolchain()
diff --git a/src/starboard/shared/win32/media_is_output_protected.cc b/src/starboard/shared/win32/media_is_output_protected.cc
new file mode 100644
index 0000000..73d2f07
--- /dev/null
+++ b/src/starboard/shared/win32/media_is_output_protected.cc
@@ -0,0 +1,21 @@
+// Copyright 2017 Google Inc. All Rights Reserved.

+//

+// Licensed under the Apache License, Version 2.0 (the "License");

+// you may not use this file except in compliance with the License.

+// You may obtain a copy of the License at

+//

+//     http://www.apache.org/licenses/LICENSE-2.0

+//

+// Unless required by applicable law or agreed to in writing, software

+// distributed under the License is distributed on an "AS IS" BASIS,

+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

+// See the License for the specific language governing permissions and

+// limitations under the License.

+

+#include "starboard/media.h"

+

+bool SbMediaIsOutputProtected() {

+  // Pretend that HDCP is always on.

+  // TODO: Fix this with proper HDCP query.

+  return true;

+}

diff --git a/src/starboard/shared/win32/player_components_impl.cc b/src/starboard/shared/win32/player_components_impl.cc
index cc86d10..fa2c4af 100644
--- a/src/starboard/shared/win32/player_components_impl.cc
+++ b/src/starboard/shared/win32/player_components_impl.cc
@@ -35,9 +35,13 @@
   using VideoRendererImpl = ::starboard::shared::win32::VideoRendererImpl;
 
   AudioDecoderImpl* audio_decoder = new AudioDecoderImpl(
-      audio_parameters.audio_codec, audio_parameters.audio_header);
+      audio_parameters.audio_codec, audio_parameters.audio_header,
+      audio_parameters.drm_system);
 
-  VideoDecoderImpl* video_decoder = new VideoDecoderImpl(video_parameters);
+  VideoDecoderImpl* video_decoder = new VideoDecoderImpl(
+      video_parameters.video_codec, video_parameters.output_mode,
+      video_parameters.decode_target_graphics_context_provider,
+      video_parameters.drm_system);
 
   AudioRendererImpl* audio_renderer =
       new AudioRendererImpl(scoped_ptr<AudioDecoder>(audio_decoder).Pass(),
diff --git a/src/starboard/shared/win32/video_decoder.cc b/src/starboard/shared/win32/video_decoder.cc
index 40fd85d..0a08c9a 100644
--- a/src/starboard/shared/win32/video_decoder.cc
+++ b/src/starboard/shared/win32/video_decoder.cc
@@ -24,17 +24,24 @@
 namespace shared {
 namespace win32 {
 
-VideoDecoder::VideoDecoder(const VideoParameters& params)
-    : video_codec_(params.video_codec),
+VideoDecoder::VideoDecoder(SbMediaVideoCodec video_codec,
+                           SbPlayerOutputMode output_mode,
+                           SbDecodeTargetGraphicsContextProvider*
+                               decode_target_graphics_context_provider,
+                           SbDrmSystem drm_system)
+    : video_codec_(video_codec),
+      drm_system_(drm_system),
       host_(NULL),
-      output_mode_(params.output_mode),
+      output_mode_(output_mode),
       decode_target_graphics_context_provider_(
-          params.decode_target_graphics_context_provider) {
-  impl_ = AbstractWin32VideoDecoder::Create(video_codec_);
+          decode_target_graphics_context_provider) {
+  impl_ = AbstractWin32VideoDecoder::Create(video_codec_, drm_system_);
   video_decoder_thread_.reset(new VideoDecoderThread(impl_.get(), this));
 }
 
 VideoDecoder::~VideoDecoder() {
+  SB_DCHECK(thread_checker_.CalledOnValidThread());
+
   video_decoder_thread_.reset(nullptr);
   impl_.reset(nullptr);
 }
@@ -71,7 +78,7 @@
   SB_DCHECK(host_);
   video_decoder_thread_.reset(nullptr);
   impl_.reset(nullptr);
-  impl_ = AbstractWin32VideoDecoder::Create(video_codec_);
+  impl_ = AbstractWin32VideoDecoder::Create(video_codec_, drm_system_);
   video_decoder_thread_.reset(new VideoDecoderThread(impl_.get(), this));
 }
 
@@ -83,7 +90,7 @@
 }
 
 void VideoDecoder::OnVideoDecoded(VideoFramePtr data) {
-  Status sts = data->IsEndOfStream() ? kBufferFull : kNeedMoreInput;
+  Status sts = (data && data->IsEndOfStream()) ? kBufferFull : kNeedMoreInput;
   host_->OnDecoderStatusUpdate(sts, data);
 }
 
diff --git a/src/starboard/shared/win32/video_decoder.h b/src/starboard/shared/win32/video_decoder.h
index 1fd2bfa..5d3734b 100644
--- a/src/starboard/shared/win32/video_decoder.h
+++ b/src/starboard/shared/win32/video_decoder.h
@@ -18,6 +18,7 @@
 #include "starboard/common/ref_counted.h"
 #include "starboard/common/scoped_ptr.h"
 #include "starboard/configuration.h"
+#include "starboard/drm.h"
 #include "starboard/shared/starboard/player/filter/player_components.h"
 #include "starboard/shared/starboard/player/filter/video_decoder_internal.h"
 #include "starboard/shared/starboard/player/input_buffer_internal.h"
@@ -38,7 +39,11 @@
       private ::starboard::shared::starboard::player::JobQueue::JobOwner,
       private VideoDecodedCallback {
  public:
-  explicit VideoDecoder(const VideoParameters& params);
+  VideoDecoder(SbMediaVideoCodec video_codec,
+               SbPlayerOutputMode output_mode,
+               SbDecodeTargetGraphicsContextProvider*
+                   decode_target_graphics_context_provider,
+               SbDrmSystem drm_system);
   ~VideoDecoder() SB_OVERRIDE;
 
   void SetHost(Host* host) SB_OVERRIDE;
@@ -53,22 +58,23 @@
   void OnVideoDecoded(VideoFramePtr data) SB_OVERRIDE;
 
  private:
+  ::starboard::shared::starboard::ThreadChecker thread_checker_;
+
   // These variables will be initialized inside ctor or SetHost() and will not
   // be changed during the life time of this class.
   const SbMediaVideoCodec video_codec_;
+  SbDrmSystem drm_system_;
   Host* host_;
 
   // Decode-to-texture related state.
-  SbPlayerOutputMode output_mode_;
-
-  SbDecodeTargetGraphicsContextProvider*
+  const SbPlayerOutputMode output_mode_;
+  SbDecodeTargetGraphicsContextProvider* const
       decode_target_graphics_context_provider_;
 
   scoped_ptr<AbstractWin32VideoDecoder> impl_;
   AtomicQueue<VideoFramePtr> decoded_data_;
-  ::starboard::Mutex mutex_;
+  Mutex mutex_;
   scoped_ptr<VideoDecoderThread> video_decoder_thread_;
-  ::starboard::shared::starboard::ThreadChecker thread_checker_;
 };
 
 }  // namespace win32
diff --git a/src/starboard/shared/win32/video_decoder_thread.cc b/src/starboard/shared/win32/video_decoder_thread.cc
index 64a7b2e..d2f8ba2 100644
--- a/src/starboard/shared/win32/video_decoder_thread.cc
+++ b/src/starboard/shared/win32/video_decoder_thread.cc
@@ -102,6 +102,7 @@
     if (number_written > 0) {
       processing_elements_.fetch_sub(static_cast<int32_t>(number_written));
       work_done = true;
+      callback_->OnVideoDecoded(NULL);
     }
 
     while (VideoFramePtr decoded_datum =
diff --git a/src/starboard/shared/win32/win32_audio_decoder.cc b/src/starboard/shared/win32/win32_audio_decoder.cc
index 0b85f30..4142764 100644
--- a/src/starboard/shared/win32/win32_audio_decoder.cc
+++ b/src/starboard/shared/win32/win32_audio_decoder.cc
@@ -103,18 +103,19 @@
 };
 
 class AbstractWin32AudioDecoderImpl : public AbstractWin32AudioDecoder,
-                                    public MediaBufferConsumerInterface {
+                                      public MediaBufferConsumerInterface {
  public:
   AbstractWin32AudioDecoderImpl(SbMediaAudioCodec codec,
-                              SbMediaAudioFrameStorageType audio_frame_fmt,
-                              SbMediaAudioSampleType sample_type,
-                              const SbMediaAudioHeader& audio_header)
+                                SbMediaAudioFrameStorageType audio_frame_fmt,
+                                SbMediaAudioSampleType sample_type,
+                                const SbMediaAudioHeader& audio_header,
+                                SbDrmSystem drm_system)
       : codec_(codec),
         audio_frame_fmt_(audio_frame_fmt),
         sample_type_(sample_type),
         audio_header_(audio_header) {
     MediaBufferConsumerInterface* media_cb = this;
-    impl_.reset(new DecoderImpl("audio", media_cb));
+    impl_.reset(new DecoderImpl("audio", media_cb, drm_system));
     EnsureAudioDecoderCreated();
   }
 
@@ -151,6 +152,8 @@
     media_buffer->Unlock();
   }
 
+  void OnNewOutputType(const ComPtr<IMFMediaType>& /*type*/) override {}
+
   ComPtr<IMFMediaType> Configure(IMFTransform* decoder) {
     ComPtr<IMFMediaType> input_type;
     HRESULT hr = MFCreateMediaType(&input_type);
@@ -209,7 +212,6 @@
     SB_DCHECK(decoder);
 
     impl_->set_decoder(decoder);
-    impl_->ActivateDecryptor(media_type);
 
     // TODO: MFWinAudioFormat_PCM?
     ComPtr<IMFMediaType> output_type =
@@ -234,17 +236,40 @@
     const int size = buff.size();
     const int64_t media_timestamp = buff.pts();
 
-    // These parameters are used for decryption. But these are not used right
-    // now and so remain empty.
     std::vector<uint8_t> key_id;
     std::vector<uint8_t> iv;
     std::vector<Subsample> subsamples;
 
+    const SbDrmSampleInfo* drm_info = buff.drm_info();
+
+    if (drm_info != NULL && drm_info->initialization_vector_size != 0) {
+      key_id.assign(drm_info->identifier,
+                    drm_info->identifier + drm_info->identifier_size);
+      iv.assign(drm_info->initialization_vector,
+                drm_info->initialization_vector +
+                    drm_info->initialization_vector_size);
+      subsamples.reserve(drm_info->subsample_count);
+      for (int32_t i = 0; i < drm_info->subsample_count; ++i) {
+        Subsample subsample = {
+            static_cast<uint32_t>(
+                drm_info->subsample_mapping[i].clear_byte_count),
+            static_cast<uint32_t>(
+                drm_info->subsample_mapping[i].encrypted_byte_count)};
+        subsamples.push_back(subsample);
+      }
+    }
+
     const std::int64_t win32_time_stamp = ConvertToWin32Time(media_timestamp);
 
     // Adjust the offset for 7 bytes to remove the ADTS header.
-    const uint8_t* audio_start = static_cast<const uint8_t*>(data) + 7;
-    const int audio_size = size - 7;
+    const int kADTSHeaderSize = 7;
+    const uint8_t* audio_start =
+        static_cast<const uint8_t*>(data) + kADTSHeaderSize;
+    const int audio_size = size - kADTSHeaderSize;
+    if (!subsamples.empty()) {
+      SB_DCHECK(subsamples[0].clear_bytes == kADTSHeaderSize);
+      subsamples[0].clear_bytes = 0;
+    }
 
     const bool write_ok = impl_->TryWriteInputBuffer(
         audio_start, audio_size, win32_time_stamp, key_id, iv, subsamples);
@@ -282,10 +307,11 @@
     SbMediaAudioCodec code,
     SbMediaAudioFrameStorageType audio_frame_fmt,
     SbMediaAudioSampleType sample_type,
-    const SbMediaAudioHeader& audio_header) {
-  return scoped_ptr<AbstractWin32AudioDecoder>(new
-    AbstractWin32AudioDecoderImpl(code, audio_frame_fmt, sample_type,
-      audio_header));
+    const SbMediaAudioHeader& audio_header,
+    SbDrmSystem drm_system) {
+  return scoped_ptr<AbstractWin32AudioDecoder>(
+      new AbstractWin32AudioDecoderImpl(code, audio_frame_fmt, sample_type,
+                                        audio_header, drm_system));
 }
 
 }  // namespace win32
diff --git a/src/starboard/shared/win32/win32_audio_decoder.h b/src/starboard/shared/win32/win32_audio_decoder.h
index 4cc5f27..3bfe78c 100644
--- a/src/starboard/shared/win32/win32_audio_decoder.h
+++ b/src/starboard/shared/win32/win32_audio_decoder.h
@@ -18,6 +18,7 @@
 #include <vector>
 
 #include "starboard/common/scoped_ptr.h"
+#include "starboard/drm.h"
 #include "starboard/media.h"
 #include "starboard/shared/starboard/player/decoded_audio_internal.h"
 #include "starboard/shared/starboard/player/video_frame_internal.h"
@@ -35,7 +36,8 @@
       SbMediaAudioCodec codec,
       SbMediaAudioFrameStorageType audio_frame_fmt,
       SbMediaAudioSampleType sample_type,
-      const SbMediaAudioHeader& audio_header);
+      const SbMediaAudioHeader& audio_header,
+      SbDrmSystem drm_system);
   virtual ~AbstractWin32AudioDecoder() {}
 
   // INPUT:
diff --git a/src/starboard/shared/win32/win32_decoder_impl.cc b/src/starboard/shared/win32/win32_decoder_impl.cc
index b06fcdc..b41b418 100644
--- a/src/starboard/shared/win32/win32_decoder_impl.cc
+++ b/src/starboard/shared/win32/win32_decoder_impl.cc
@@ -117,11 +117,15 @@
 }  // namespace
 
 DecoderImpl::DecoderImpl(const std::string& type,
-                         MediaBufferConsumerInterface* media_buffer_consumer)
+                         MediaBufferConsumerInterface* media_buffer_consumer,
+                         SbDrmSystem drm_system)
     : type_(type),
       media_buffer_consumer_(media_buffer_consumer),
       discontinuity_(false) {
   SB_DCHECK(media_buffer_consumer_);
+  drm_system_ =
+      static_cast<::starboard::xb1::shared::playready::SbDrmSystemPlayready*>(
+          drm_system);
 }
 
 DecoderImpl::~DecoderImpl() {
@@ -137,13 +141,29 @@
   return decoder;
 }
 
-void DecoderImpl::ActivateDecryptor(ComPtr<IMFMediaType> input_type) {
+void DecoderImpl::ActivateDecryptor() {
+  SB_DCHECK(decoder_);
+
   if (!decryptor_) {
     return;
   }
 
-  HRESULT hr = decryptor_->SetInputType(kStreamId, input_type.Get(),
-                                        0);  // MFT_SET_TYPE_TEST_FLAGS.
+  ComPtr<IMFMediaType> decryptor_input_type;
+
+  HRESULT hr = decoder_->GetInputCurrentType(
+      kStreamId, decryptor_input_type.GetAddressOf());
+  CheckResult(hr);
+
+  GUID original_sub_type;
+  {
+    ComPtr<IMFMediaType> output_type;
+    hr = decoder_->GetOutputCurrentType(kStreamId, output_type.GetAddressOf());
+    CheckResult(hr);
+    output_type->GetGUID(MF_MT_SUBTYPE, &original_sub_type);
+  }
+
+  const DWORD kFlags = 0;
+  hr = decryptor_->SetInputType(kStreamId, decryptor_input_type.Get(), kFlags);
   CheckResult(hr);
 
   // Ensure that the decryptor and the decoder agrees on the protection of
@@ -158,7 +178,6 @@
   BYTE* crypt_seed = NULL;
   DWORD crypt_seed_size = 0;
   ComPtr<IMFMediaType> decoder_input_type;
-  ComPtr<IMFMediaType> decoder_output_type;
 
   hr = decryptor_.As(&decryption_sample_protection);
   CheckResult(hr);
@@ -211,6 +230,21 @@
   // Start the decryptor, note that this should be better abstracted.
   SendMFTMessage(decryptor_.Get(), MFT_MESSAGE_NOTIFY_BEGIN_STREAMING);
   SendMFTMessage(decryptor_.Get(), MFT_MESSAGE_NOTIFY_START_OF_STREAM);
+
+  for (int index = 0;; ++index) {
+    ComPtr<IMFMediaType> output_type;
+    hr = decoder_->GetOutputAvailableType(0, index, &output_type);
+    if (SUCCEEDED(hr)) {
+      GUID sub_type;
+      output_type->GetGUID(MF_MT_SUBTYPE, &sub_type);
+      if (IsEqualGUID(sub_type, original_sub_type)) {
+        hr = decoder_->SetOutputType(0, output_type.Get(), 0);
+        CheckResult(hr);
+        break;
+      }
+    }
+    output_type.Reset();
+  }
 }
 
 bool DecoderImpl::TryWriteInputBuffer(
@@ -236,6 +270,19 @@
   // Better check the key id size is 16 and iv size is 8 or 16.
   bool encrypted = !key_id.empty() && !iv.empty();
   if (encrypted) {
+    if (!decryptor_) {
+      scoped_refptr<::starboard::xb1::shared::playready::PlayreadyLicense>
+          license = drm_system_->GetLicense(key_id.data(),
+                                            static_cast<int>(key_id.size()));
+      if (license && license->usable()) {
+        decryptor_ = license->decryptor();
+        ActivateDecryptor();
+      }
+    }
+    if (!decryptor_) {
+      SB_NOTREACHED();
+      return false;
+    }
     size_t iv_size = iv.size();
     const char kEightZeros[8] = {0};
     if (iv_size == 16 && SbMemoryCompare(iv.data() + 8, kEightZeros, 8) == 0) {
@@ -315,27 +362,33 @@
 
 bool DecoderImpl::DeliverOneOutputOnAllTransforms() {
   SB_DCHECK(decoder_);
-  bool delivered = false;
   if (decryptor_) {
-    if (ComPtr<IMFSample> sample = DeliverOutputOnTransform(decryptor_)) {
-      HRESULT hr = decoder_->ProcessInput(kStreamId, sample.Get(), 0);
+    if (!cached_decryptor_output_) {
+      cached_decryptor_output_ = DeliverOutputOnTransform(decryptor_);
+    }
+    while (cached_decryptor_output_) {
+      HRESULT hr =
+          decoder_->ProcessInput(kStreamId, cached_decryptor_output_.Get(), 0);
       if (hr == MF_E_NOTACCEPTING) {
-        // The protocol says that when ProcessInput() returns MF_E_NOTACCEPTING,
-        // there must be some output available. Retrieve the output and the next
-        // ProcessInput() should succeed.
-        ComPtr<IMFSample> sample_inner = DeliverOutputOnTransform(decoder_);
-        if (sample_inner) {
-          DeliverDecodedSample(sample_inner);
-          delivered = true;
-        }
-        hr = decoder_->ProcessInput(kStreamId, sample.Get(), 0);
+        break;
+      } else {
         CheckResult(hr);
-        return delivered;
+        cached_decryptor_output_ = DeliverOutputOnTransform(decryptor_);
       }
-      CheckResult(hr);
-      delivered = true;
+    }
+    if (cached_decryptor_output_) {
+      // The protocol says that when ProcessInput() returns MF_E_NOTACCEPTING,
+      // there must be some output available. Retrieve the output and the next
+      // ProcessInput() should succeed.
+      ComPtr<IMFSample> decoder_output = DeliverOutputOnTransform(decoder_);
+      if (decoder_output) {
+        DeliverDecodedSample(decoder_output);
+      }
+      return decoder_output != NULL;
     }
   }
+
+  bool delivered = false;
   if (ComPtr<IMFSample> sample = DeliverOutputOnTransform(decoder_)) {
     DeliverDecodedSample(sample);
     delivered = true;
@@ -371,18 +424,6 @@
   HRESULT hr = transform->GetOutputStreamInfo(kStreamId, &output_stream_info);
   CheckResult(hr);
 
-  // Each media sample (IMFSample interface) of output data from the MFT
-  // contains complete, unbroken units of data. The definition of a unit
-  // of data depends on the media type: For uncompressed video, a video
-  // frame; for compressed data, a compressed packet; for uncompressed audio,
-  // a single audio frame.
-  //
-  // For uncompressed audio formats, this flag is always implied. (It is valid
-  // to set the flag, but not required.) An uncompressed audio frame should
-  // never span more than one media sample.
-  SB_DCHECK((output_stream_info.dwFlags & MFT_OUTPUT_STREAM_WHOLE_SAMPLES) !=
-            0);
-
   if (StreamAllocatesMemory(output_stream_info.dwFlags)) {
     // Try to let the IMFTransform allocate the memory if possible.
     return;
@@ -431,9 +472,21 @@
 
     hr = transform->SetOutputType(kStreamId, media_type.Get(), 0);
     CheckResult(hr);
-    return NULL;
-  } else if (hr == MF_E_TRANSFORM_NEED_MORE_INPUT ||
-             output_data_buffer.dwStatus == MFT_OUTPUT_DATA_BUFFER_NO_SAMPLE) {
+
+    media_buffer_consumer_->OnNewOutputType(media_type);
+
+    hr = transform->ProcessOutput(kFlags, kNumberOfBuffers, &output_data_buffer,
+                                  &status);
+
+    SB_DCHECK(!output_data_buffer.pEvents);
+
+    output = output_data_buffer.pSample;
+    ReleaseIfNotNull(&output_data_buffer.pEvents);
+    ReleaseIfNotNull(&output_data_buffer.pSample);
+  }
+
+  if (hr == MF_E_TRANSFORM_NEED_MORE_INPUT ||
+      output_data_buffer.dwStatus == MFT_OUTPUT_DATA_BUFFER_NO_SAMPLE) {
     return NULL;
   }
 
diff --git a/src/starboard/shared/win32/win32_decoder_impl.h b/src/starboard/shared/win32/win32_decoder_impl.h
index 38374ff..cef0d5d 100644
--- a/src/starboard/shared/win32/win32_decoder_impl.h
+++ b/src/starboard/shared/win32/win32_decoder_impl.h
@@ -25,10 +25,14 @@
 #include <vector>
 
 #include "starboard/common/scoped_ptr.h"
+#include "starboard/drm.h"
 #include "starboard/media.h"
 #include "starboard/shared/win32/media_common.h"
 #include "starboard/types.h"
 
+// TODO: Win32 code shouldn't depend on Xb1 code.  Refactor this.
+#include "starboard/xb1/shared/playready/drm_system_playready.h"
+
 namespace starboard {
 namespace shared {
 namespace win32 {
@@ -38,6 +42,8 @@
   virtual void Consume(Microsoft::WRL::ComPtr<IMFMediaBuffer> media_buffer,
                        int64_t win32_timestamp) = 0;
 
+  virtual void OnNewOutputType(const ComPtr<IMFMediaType>& type) = 0;
+
  protected:
   ~MediaBufferConsumerInterface() {}
 };
@@ -50,12 +56,13 @@
 class DecoderImpl {
  public:
   DecoderImpl(const std::string& type,
-              MediaBufferConsumerInterface* media_buffer_consumer);
+              MediaBufferConsumerInterface* media_buffer_consumer,
+              SbDrmSystem drm_system);
   ~DecoderImpl();
 
   static Microsoft::WRL::ComPtr<IMFTransform> CreateDecoder(CLSID clsid);
 
-  void ActivateDecryptor(Microsoft::WRL::ComPtr<IMFMediaType> input_type);
+  void ActivateDecryptor();
   bool TryWriteInputBuffer(const void* data,
                            int size,
                            std::int64_t win32_timestamp,
@@ -84,9 +91,11 @@
   const std::string type_;  // For debugging purpose.
   // This is the target for the all completed media buffers.
   MediaBufferConsumerInterface* media_buffer_consumer_;
+  ::starboard::xb1::shared::playready::SbDrmSystemPlayready* drm_system_;
   bool discontinuity_;
 
   Microsoft::WRL::ComPtr<IMFTransform> decryptor_;
+  Microsoft::WRL::ComPtr<IMFSample> cached_decryptor_output_;
   Microsoft::WRL::ComPtr<IMFTransform> decoder_;
 };
 
diff --git a/src/starboard/shared/win32/win32_video_decoder.cc b/src/starboard/shared/win32/win32_video_decoder.cc
index 93bc487..667b2e5 100644
--- a/src/starboard/shared/win32/win32_video_decoder.cc
+++ b/src/starboard/shared/win32/win32_video_decoder.cc
@@ -36,31 +36,22 @@
 // This magic number is taken directly from sample from MS.  Can be further
 // tuned in case if there is playback issues.
 const int kSampleAllocatorFramesMax = 5;
+// This is the minimum allocated frames in the output ring buffer.  Can be
+// further tuned to save memory.  Note that use a value that is too small leads
+// to hang when calling ProcessOutput().
+const int kMinimumOutputSampleCount = 10;
 
 // CLSID_CMSVideoDecoderMFT {62CE7E72-4C71-4D20-B15D-452831A87D9D}
 const GUID CLSID_VideoDecoder = {0x62CE7E72, 0x4C71, 0x4d20, 0xB1, 0x5D, 0x45,
                                  0x28,       0x31,   0xA8,   0x7D, 0x9D};
 
-ComPtr<ID3D11Texture2D> GetTexture2D(ComPtr<IMFMediaBuffer> media_buffer) {
-  ComPtr<IMFDXGIBuffer> dxgi_buffer;
-  HRESULT hr = media_buffer.As(&dxgi_buffer);
-  CheckResult(hr);
-  SB_DCHECK(dxgi_buffer.Get());
-
-  ComPtr<ID3D11Texture2D> dx_texture;
-  hr = dxgi_buffer->GetResource(IID_PPV_ARGS(&dx_texture));
-  CheckResult(hr);
-  return dx_texture;
-}
-
 class VideoFrameFactory {
  public:
   static VideoFramePtr Construct(SbMediaTime timestamp,
-                                 ComPtr<IMFMediaBuffer> media_buffer) {
-    ComPtr<ID3D11Texture2D> texture = GetTexture2D(media_buffer);
-    D3D11_TEXTURE2D_DESC tex_desc;
-    texture->GetDesc(&tex_desc);
-    VideoFramePtr out(new VideoFrame(tex_desc.Width, tex_desc.Height,
+                                 ComPtr<IMFMediaBuffer> media_buffer,
+                                 uint32_t width,
+                                 uint32_t height) {
+    VideoFramePtr out(new VideoFrame(width, height,
                                      timestamp, media_buffer.Detach(),
                                      nullptr, DeleteTextureFn));
     frames_in_flight_.increment();
@@ -84,10 +75,10 @@
 class AbstractWin32VideoDecoderImpl : public AbstractWin32VideoDecoder,
                                     public MediaBufferConsumerInterface {
  public:
-  explicit AbstractWin32VideoDecoderImpl(SbMediaVideoCodec codec)
+  AbstractWin32VideoDecoderImpl(SbMediaVideoCodec codec, SbDrmSystem drm_system)
       : codec_(codec), surface_width_(0), surface_height_(0) {
     MediaBufferConsumerInterface* media_cb = this;
-    impl_.reset(new DecoderImpl("video", media_cb));
+    impl_.reset(new DecoderImpl("video", media_cb, drm_system));
     EnsureVideoDecoderCreated();
   }
 
@@ -95,10 +86,32 @@
                        int64_t win32_timestamp) {
     const SbMediaTime media_timestamp = ConvertToMediaTime(win32_timestamp);
     VideoFramePtr frame_output =
-        VideoFrameFactory::Construct(media_timestamp, media_buffer);
+        VideoFrameFactory::Construct(media_timestamp, media_buffer,
+            surface_width_, surface_height_);
     output_queue_.PushBack(frame_output);
   }
 
+  void OnNewOutputType(const ComPtr<IMFMediaType>& type) override {
+    MFVideoArea aperture;
+    HRESULT hr = type->GetBlob(MF_MT_MINIMUM_DISPLAY_APERTURE,
+        reinterpret_cast<UINT8*>(&aperture), sizeof(MFVideoArea), nullptr);
+    if (SUCCEEDED(hr)) {
+      // TODO: consider offset as well
+      surface_width_ = aperture.Area.cx;
+      surface_height_ = aperture.Area.cy;
+      return;
+    }
+
+    uint32_t width;
+    uint32_t height;
+    hr = MFGetAttributeSize(type.Get(), MF_MT_FRAME_SIZE,
+                            &width, &height);
+    if (SUCCEEDED(hr)) {
+      surface_width_ = width;
+      surface_height_ = height;
+    }
+  }
+
   ComPtr<IMFMediaType> Configure(IMFTransform* decoder) {
     ComPtr<IMFMediaType> input_type;
     HRESULT hr = MFCreateMediaType(&input_type);
@@ -160,8 +173,6 @@
     SB_DCHECK(1 == input_stream_count);
     SB_DCHECK(1 == output_stream_count);
 
-    impl_->ActivateDecryptor(media_type);
-
     ComPtr<IMFMediaType> output_type =
         FindMediaType(MFVideoFormat_YV12, decoder.Get());
 
@@ -174,6 +185,10 @@
     hr = decoder->GetAttributes(attributes.GetAddressOf());
     CheckResult(hr);
 
+    hr = attributes->SetUINT32(MF_SA_MINIMUM_OUTPUT_SAMPLE_COUNT,
+                               kMinimumOutputSampleCount);
+    CheckResult(hr);
+
     UINT32 value = 0;
     hr = attributes->GetUINT32(MFT_SUPPORT_DYNAMIC_FORMAT_CHANGE, &value);
     SB_DCHECK(hr == S_OK || hr == MF_E_ATTRIBUTENOTFOUND);
@@ -225,19 +240,35 @@
       return false;  // Wait for more data.
     }
 
-    // This would be used for decrypting content.
-    // For now this is empty.
     std::vector<uint8_t> key_id;
     std::vector<uint8_t> iv;
+    std::vector<Subsample> subsamples;
 
-    std::vector<Subsample> empty_subsample;
+    // TODO: Merge this with similar code in the audio decoder.
+    const SbDrmSampleInfo* drm_info = buff->drm_info();
+
+    if (drm_info != NULL && drm_info->initialization_vector_size != 0) {
+      key_id.assign(drm_info->identifier,
+                    drm_info->identifier + drm_info->identifier_size);
+      iv.assign(drm_info->initialization_vector,
+                drm_info->initialization_vector +
+                    drm_info->initialization_vector_size);
+      subsamples.reserve(drm_info->subsample_count);
+      for (int32_t i = 0; i < drm_info->subsample_count; ++i) {
+        Subsample subsample = {
+            static_cast<uint32_t>(
+                drm_info->subsample_mapping[i].clear_byte_count),
+            static_cast<uint32_t>(
+                drm_info->subsample_mapping[i].encrypted_byte_count)};
+        subsamples.push_back(subsample);
+      }
+    }
 
     const SbMediaTime media_timestamp = buff->pts();
     const int64_t win32_timestamp = ConvertToWin32Time(media_timestamp);
 
     const bool write_ok = impl_->TryWriteInputBuffer(
-        buff->data(), buff->size(), win32_timestamp,
-        key_id, iv, empty_subsample);
+        buff->data(), buff->size(), win32_timestamp, key_id, iv, subsamples);
 
     return write_ok;
   }
@@ -271,12 +302,14 @@
   uint32_t surface_height_;
   HardwareDecoderContext dx_decoder_ctx_;
 };
+
 }  // anonymous namespace.
 
 scoped_ptr<AbstractWin32VideoDecoder> AbstractWin32VideoDecoder::Create(
-    SbMediaVideoCodec codec) {
+    SbMediaVideoCodec codec,
+    SbDrmSystem drm_system) {
   return scoped_ptr<AbstractWin32VideoDecoder>(
-      new AbstractWin32VideoDecoderImpl(codec));
+      new AbstractWin32VideoDecoderImpl(codec, drm_system));
 }
 
 }  // namespace win32
diff --git a/src/starboard/shared/win32/win32_video_decoder.h b/src/starboard/shared/win32/win32_video_decoder.h
index 84ff7b7..a32158b 100644
--- a/src/starboard/shared/win32/win32_video_decoder.h
+++ b/src/starboard/shared/win32/win32_video_decoder.h
@@ -20,6 +20,7 @@
 
 #include "starboard/common/ref_counted.h"
 #include "starboard/common/scoped_ptr.h"
+#include "starboard/drm.h"
 #include "starboard/media.h"
 #include "starboard/shared/starboard/player/decoded_audio_internal.h"
 #include "starboard/shared/starboard/player/video_frame_internal.h"
@@ -33,7 +34,8 @@
 // VideoDecoder for Win32.
 class AbstractWin32VideoDecoder {
  public:
-  static scoped_ptr<AbstractWin32VideoDecoder> Create(SbMediaVideoCodec codec);
+  static scoped_ptr<AbstractWin32VideoDecoder> Create(SbMediaVideoCodec codec,
+                                                      SbDrmSystem drm_system);
   virtual ~AbstractWin32VideoDecoder() {}
 
   virtual bool TryWrite(const scoped_refptr<InputBuffer>& buff) = 0;
diff --git a/src/starboard/shared/x11/application_x11.cc b/src/starboard/shared/x11/application_x11.cc
index 19fb1f2..c4a5cb6 100644
--- a/src/starboard/shared/x11/application_x11.cc
+++ b/src/starboard/shared/x11/application_x11.cc
@@ -43,6 +43,9 @@
 namespace starboard {
 namespace shared {
 namespace x11 {
+
+using ::starboard::shared::dev_input::DevInput;
+
 namespace {
 
 enum {
@@ -647,28 +650,6 @@
 }
 #endif
 
-bool XNextEventTimed(Display* display, XEvent* out_event, SbTime duration) {
-  if (XPending(display) == 0) {
-    if (duration <= SbTime()) {
-      return false;
-    }
-
-    int fd = ConnectionNumber(display);
-    fd_set read_set;
-    FD_ZERO(&read_set);
-    FD_SET(fd, &read_set);
-    struct timeval tv;
-    SbTime clamped_duration = std::max(duration, SbTime());
-    ToTimevalDuration(clamped_duration, &tv);
-    if (select(fd + 1, &read_set, NULL, NULL, &tv) == 0) {
-      return false;
-    }
-  }
-
-  XNextEvent(display, out_event);
-  return true;
-}
-
 void XSendAtom(Window window, Atom atom) {
   // XLib is not thread-safe. Since we may be coming from another thread, we
   // have to open another connection to the display to inject the wake-up event.
@@ -732,6 +713,10 @@
 
   SbWindow window = new SbWindowPrivate(display_, options);
   windows_.push_back(window);
+  if (!dev_input_) {
+    // evdev input will be sent to the first created window only.
+    dev_input_.reset(DevInput::Create(window, ConnectionNumber(display_)));
+  }
   return window;
 }
 
@@ -745,6 +730,12 @@
       std::find(windows_.begin(), windows_.end(), window);
   SB_DCHECK(iterator != windows_.end());
   windows_.erase(iterator);
+
+  if (windows_.empty()) {
+    SB_DCHECK(dev_input_);
+    dev_input_.reset();
+  }
+
   delete window;
   if (windows_.empty()) {
     StopX();
@@ -849,23 +840,29 @@
 ApplicationX11::WaitForSystemEventWithTimeout(SbTime time) {
   SB_DCHECK(display_);
 
-  XEvent x_event;
-
   shared::starboard::Application::Event* pending_event = GetPendingEvent();
   if (pending_event) {
     return pending_event;
   }
 
-  if (XNextEventTimed(display_, &x_event, time)) {
+  SB_DCHECK(dev_input_);
+  shared::starboard::Application::Event* evdev_event =
+      dev_input_->WaitForSystemEventWithTimeout(time);
+
+  if (!evdev_event && XPending(display_) != 0) {
+    XEvent x_event;
+    XNextEvent(display_, &x_event);
     return XEventToEvent(&x_event);
-  } else {
-    return NULL;
   }
+
+  return evdev_event;
 }
 
 void ApplicationX11::WakeSystemEventWait() {
   SB_DCHECK(!windows_.empty());
   XSendAtom((*windows_.begin())->window, wake_up_atom_);
+  SB_DCHECK(dev_input_);
+  dev_input_->WakeSystemEventWait();
 }
 
 bool ApplicationX11::EnsureX() {
@@ -879,7 +876,7 @@
   XSetErrorHandler(ErrorHandler);
   display_ = XOpenDisplay(NULL);
   if (!display_) {
-    const char *display_environment = getenv("DISPLAY");
+    const char* display_environment = getenv("DISPLAY");
     if (display_environment == NULL) {
       SB_LOG(ERROR) << "Unable to open display, DISPLAY not set.";
     } else {
diff --git a/src/starboard/shared/x11/application_x11.h b/src/starboard/shared/x11/application_x11.h
index a0235b1..cadfc90 100644
--- a/src/starboard/shared/x11/application_x11.h
+++ b/src/starboard/shared/x11/application_x11.h
@@ -20,9 +20,11 @@
 #include <queue>
 #include <vector>
 
+#include "base/memory/scoped_ptr.h"
 #include "starboard/configuration.h"
 #include "starboard/player.h"
 #include "starboard/shared/internal_only.h"
+#include "starboard/shared/linux/dev_input/dev_input.h"
 #include "starboard/shared/starboard/application.h"
 #include "starboard/shared/starboard/queue_application.h"
 #include "starboard/types.h"
@@ -117,6 +119,9 @@
   // Indicates whether a key press event that requires a matching release has
   // been dispatched.
   bool paste_buffer_key_release_pending_;
+
+  // The /dev/input input handler. Only set when there is an open window.
+  scoped_ptr<::starboard::shared::dev_input::DevInput> dev_input_;
 };
 
 }  // namespace x11