[M120 Migration][MM] Support W3C EME 63/307263/4
authorwuxiaoliang <xliang.wu@samsung.com>
Wed, 6 Mar 2024 01:46:46 +0000 (09:46 +0800)
committerBot Blink <blinkbot@samsung.com>
Thu, 7 Mar 2024 03:59:57 +0000 (03:59 +0000)
Add IEMEDrmBridge for encrypted stream.
single process -> espp decrypted stream
multiple process -> drm decrypted stream -> pass tz_handle to espp

Migration from:
https://review.tizen.org/gerrit/#/c/platform/framework/web/chromium-efl/+/290566/
https://review.tizen.org/gerrit/#/c/platform/framework/web/chromium-efl/+/291049/
https://review.tizen.org/gerrit/#/c/platform/framework/web/chromium-efl/+/296545/
https://review.tizen.org/gerrit/#/c/platform/framework/web/chromium-efl/+/298081/
https://review.tizen.org/gerrit/#/c/platform/framework/web/chromium-efl/+/297184/

Change-Id: Ibb604997638c6ff330f33159a3ee777122767cbe
Signed-off-by: wuxiaoliang <xliang.wu@samsung.com>
25 files changed:
content/renderer/media/media_factory.cc
media/base/decoder_buffer.cc
media/base/decoder_buffer.h
media/base/decryptor.cc
media/base/decryptor.h
media/base/key_systems.cc
media/base/pipeline_impl.cc
media/filters/decrypting_demuxer_stream.cc
media/mojo/common/media_type_converters.cc
media/mojo/mojom/media_types.mojom
packaging/chromium-efl.spec
tizen_src/build/config/BUILD.gn
tizen_src/build/config/tizen_features.gni
tizen_src/chromium_impl/content/renderer/media/cdm/render_cdm_factory.cc [new file with mode: 0644]
tizen_src/chromium_impl/content/renderer/media/cdm/render_cdm_factory.h [new file with mode: 0644]
tizen_src/chromium_impl/content/renderer/renderer_efl.gni
tizen_src/chromium_impl/media/filters/esplusplayer_util.cc
tizen_src/chromium_impl/media/filters/ieme_drm_bridge.cc [new file with mode: 0644]
tizen_src/chromium_impl/media/filters/ieme_drm_bridge.h [new file with mode: 0644]
tizen_src/chromium_impl/media/filters/media_player_esplusplayer_tv.cc
tizen_src/chromium_impl/media/filters/media_player_esplusplayer_tv.h
tizen_src/chromium_impl/media/media_efl.gni
tizen_src/ewk/efl_integration/public/ewk_media_playback_info_product.h
tizen_src/ewk/efl_integration/renderer/content_renderer_client_efl.cc
tizen_src/ewk/efl_integration/renderer/content_renderer_client_efl.h

index 500f254..ef9d506 100644 (file)
 #include "media/mojo/mojom/speech_recognition_service.mojom.h"
 #endif  // BUILDFLAG(IS_WIN)
 
+#if BUILDFLAG(IS_TIZEN_TV)
+#include "content/renderer/media/cdm/render_cdm_factory.h"
+#endif
+
 namespace {
 
 // This limit is much higher than it needs to be right now, because the logic
@@ -879,6 +883,8 @@ media::CdmFactory* MediaFactory::GetCdmFactory() {
   DCHECK(interface_broker_);
   cdm_factory_ = std::make_unique<media::FuchsiaCdmFactory>(
       std::make_unique<media::MojoFuchsiaCdmProvider>(interface_broker_));
+#elif BUILDFLAG(IS_TIZEN_TV)
+  cdm_factory_.reset(new RenderCdmFactory());
 #elif BUILDFLAG(ENABLE_MOJO_CDM)
   cdm_factory_ =
       std::make_unique<media::MojoCdmFactory>(GetMediaInterfaceFactory());
index 2c2a5a0..282ca15 100644 (file)
@@ -9,6 +9,10 @@
 #include "base/debug/alias.h"
 #include "media/base/subsample_entry.h"
 
+#if BUILDFLAG(IS_TIZEN_TV)
+#include "base/logging.h"
+#endif
+
 namespace media {
 
 DecoderBuffer::TimeInfo::TimeInfo() = default;
@@ -216,4 +220,16 @@ void DecoderBuffer::set_timestamp(base::TimeDelta timestamp) {
   time_info_.timestamp = timestamp;
 }
 
+#if BUILDFLAG(IS_TIZEN_TV)
+scoped_refptr<DecoderBuffer> DecoderBuffer::CreateTzHandleBuffer(int handle,
+                                                                 int size) {
+  auto ptr = scoped_refptr<DecoderBuffer>(new DecoderBuffer(NULL, 0));
+  ptr->tz_handle_ = handle;
+  ptr->tz_buffer_size_ = size;
+  LOG(INFO) << __func__ << "tz_handle : " << handle
+            << " ; tz_buffer_size_ : " << size;
+  return ptr;
+}
+#endif
+
 }  // namespace media
index 673e685..7113a5e 100644 (file)
 #include "media/base/timestamp_constants.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 
+#if BUILDFLAG(IS_TIZEN_TV)
+#include "tizen_src/chromium_impl/build/tizen_version.h"
+
+#if TIZEN_VERSION_AT_LEAST(6, 0, 0)
+#include "base/scoped_generic.h"
+
+#include <drmdecrypt/emeCDM/IEME.h>
+#endif
+#endif
+
 namespace media {
 
 // A specialized buffer for interfacing with audio / video decoders.
@@ -141,6 +151,11 @@ class MEDIA_EXPORT DecoderBuffer
     return time_info_.timestamp;
   }
 
+#if BUILDFLAG(IS_TIZEN_TV)
+  static scoped_refptr<DecoderBuffer> CreateTzHandleBuffer(int handle,
+                                                           int size);
+#endif
+
   // TODO(dalecurtis): This should be renamed at some point, but to avoid a yak
   // shave keep as a virtual with hacker_style() for now.
   virtual void set_timestamp(base::TimeDelta timestamp);
@@ -207,8 +222,13 @@ class MEDIA_EXPORT DecoderBuffer
 
   // If there's no data in this buffer, it represents end of stream.
   bool end_of_stream() const {
+#if BUILDFLAG(IS_TIZEN_TV)
+    return !read_only_mapping_.IsValid() && !writable_mapping_.IsValid() &&
+           !external_memory_ && !data_ && !tz_handle_;
+#else
     return !read_only_mapping_.IsValid() && !writable_mapping_.IsValid() &&
            !external_memory_ && !data_;
+#endif
   }
 
   bool is_key_frame() const {
@@ -246,6 +266,39 @@ class MEDIA_EXPORT DecoderBuffer
   // Returns a human-readable string describing |*this|.
   std::string AsHumanReadableString(bool verbose = false) const;
 
+#if BUILDFLAG(IS_TIZEN_TV)
+  size_t tz_handle() const {
+    DCHECK(!end_of_stream());
+    return tz_handle_;
+  }
+
+  void set_tz_handle(size_t tz_handle) {
+    DCHECK(!end_of_stream());
+    tz_handle_ = tz_handle;
+  }
+
+  size_t tz_buffer_size() const {
+    DCHECK(!end_of_stream());
+    return tz_buffer_size_;
+  }
+
+  void set_tz_buffer_size(size_t tz_buffer_size) {
+    DCHECK(!end_of_stream());
+    tz_buffer_size_ = tz_buffer_size;
+  }
+
+#if TIZEN_VERSION_AT_LEAST(6, 0, 0)
+  void set_decryptor_handle(eme::eme_decryptor_t decryptor_handle) {
+    DCHECK(!end_of_stream());
+    decryptor_handle_ = decryptor_handle;
+  }
+  size_t decryptor_handle() const {
+    DCHECK(!end_of_stream());
+    return decryptor_handle_;
+  }
+#endif
+#endif
+
  protected:
   friend class base::RefCountedThreadSafe<DecoderBuffer>;
 
@@ -287,6 +340,12 @@ class MEDIA_EXPORT DecoderBuffer
   // Encryption parameters for the encoded data.
   std::unique_ptr<DecryptConfig> decrypt_config_;
 
+#if BUILDFLAG(IS_TIZEN_TV)
+  size_t decryptor_handle_{0};
+  size_t tz_handle_{0};
+  size_t tz_buffer_size_{0};
+#endif
+
   // Whether the frame was marked as a keyframe in the container.
   bool is_key_frame_ = false;
 
index fe5fcb3..f4b02e7 100644 (file)
@@ -17,6 +17,10 @@ const char* Decryptor::GetStatusName(Status status) {
       return "need_more_data";
     case kError:
       return "error";
+#if BUILDFLAG(IS_TIZEN_TV)
+    case kBufferFull:
+      return "buffer_full";
+#endif
   }
 }
 
index 5e67e62..03c63e1 100644 (file)
 #include "media/base/audio_buffer.h"
 #include "media/base/media_export.h"
 
+#if BUILDFLAG(IS_TIZEN_TV)
+#include "media/base/decoder_buffer.h"
+
+#include <drmdecrypt/emeCDM/IEME.h>
+#endif
+
 namespace media {
 
 class AudioDecoderConfig;
@@ -31,10 +37,20 @@ class MEDIA_EXPORT Decryptor {
     kSuccess,  // Decryption successfully completed. Decrypted buffer ready.
     kNoKey,    // No key is available to decrypt.
     kNeedMoreData,  // Decoder needs more data to produce a frame.
-    kError,         // Key is available but an error occurred during decryption.
+#if BUILDFLAG(IS_TIZEN_TV)
+    kBufferFull,  // Decrypt buffer is full.
+#endif
+    kError,  // Key is available but an error occurred during decryption.
     kStatusMax = kError
   };
 
+#if BUILDFLAG(IS_TIZEN_TV)
+  enum class DecryptBufferFullStrategy {
+    kRetryDecryption,  // Retry decryption when buffer is full. This is default.
+    kAbortDecryption,  // Return decryption error when buffer is full.
+  };
+#endif
+
   static const char* GetStatusName(Status status);
 
   enum StreamType { kAudio, kVideo, kStreamTypeMax = kVideo };
@@ -73,6 +89,17 @@ class MEDIA_EXPORT Decryptor {
                        scoped_refptr<DecoderBuffer> encrypted,
                        DecryptCB decrypt_cb) = 0;
 
+#if BUILDFLAG(IS_TIZEN_TV)
+  // If DecryptCB is oncecallback, we only need to cancel the pending decrpt
+  // But it can't run the oncecallback again when cancel decrypt.
+  // So change it to repeating callback which is same with M76
+  using DecryptCBTizen =
+      base::RepeatingCallback<void(Status, scoped_refptr<DecoderBuffer>)>;
+  virtual void DecryptTizen(StreamType stream_type,
+                            scoped_refptr<DecoderBuffer> encrypted,
+                            DecryptCBTizen decrypt_cb) {}
+#endif
+
   // Cancels the scheduled decryption operation for |stream_type| and fires the
   // pending DecryptCB immediately with kSuccess and NULL.
   // Decrypt() should not be called again before the pending DecryptCB for the
@@ -92,6 +119,17 @@ class MEDIA_EXPORT Decryptor {
   virtual void InitializeVideoDecoder(const VideoDecoderConfig& config,
                                       DecoderInitCB init_cb) = 0;
 
+#if BUILDFLAG(IS_TIZEN_TV)
+  // Returns decryptor handle
+  virtual eme::eme_decryptor_t GetDecryptorHandle() {
+    return eme::eme_decryptor_t{0};
+  }
+
+  // Set the strategy used when "buffer full" error is returned
+  // from decryptor.
+  virtual void SetDecryptStrategy(DecryptBufferFullStrategy) {}
+#endif
+
   // Helper structure for managing multiple decoded audio buffers per input.
   typedef std::list<scoped_refptr<AudioBuffer> > AudioFrames;
 
index dccc084..185117c 100644 (file)
@@ -236,6 +236,14 @@ static bool IsPotentiallySupportedKeySystem(const std::string& key_system) {
     return true;
   }
 
+#if BUILDFLAG(IS_TIZEN_TV)
+  // Playready key systems are potentially supported
+  const char kPlayreadyKeySystemSuffix[] = ".playready";
+  if (base::EndsWith(key_system, kPlayreadyKeySystemSuffix,
+                     base::CompareCase::SENSITIVE))
+    return true;
+#endif
+
   // External or MediaFoundation Clear Key is known and supports suffixes for
   // testing.
   if (IsExternalClearKey(key_system))
index bf67b12..bfe9c8e 100644 (file)
 #include "media/base/win/mf_feature_checks.h"
 #endif  // BUILDFLAG(IS_WIN)
 
+#if BUILDFLAG(IS_TIZEN_TV)
+#include "media/filters/decrypting_media_resource.h"
+#endif
+
 static const double kDefaultPlaybackRate = 0.0;
 static const float kDefaultVolume = 1.0f;
 
@@ -215,6 +219,12 @@ class PipelineImpl::RendererWrapper final : public DemuxerHost,
   void DestroyRenderer();
   void ReportMetadata(StartType start_type);
 
+#if BUILDFLAG(IS_TIZEN_TV)
+  // Create decrypting media resource
+  void CreateAndInitializeDecryptingMediaResource();
+  void OnDecryptInitDone(bool success);
+#endif
+
   // Returns whether there's any encrypted stream in the demuxer.
   bool HasEncryptedStream();
 
@@ -273,6 +283,7 @@ class PipelineImpl::RendererWrapper final : public DemuxerHost,
 
 #if BUILDFLAG(IS_TIZEN_TV)
   std::string mime_type_;
+  std::unique_ptr<DecryptingMediaResource> decrypting_media_resource_{nullptr};
 #endif
 
   // Whether we've received the audio/video ended events.
@@ -694,6 +705,31 @@ void PipelineImpl::RendererWrapper::SetContentMimeType(
 }
 #endif
 
+#if BUILDFLAG(IS_TIZEN_TV)
+void PipelineImpl::RendererWrapper::OnDecryptInitDone(bool success) {
+  DCHECK(media_task_runner_->RunsTasksInCurrentSequence());
+  LOG(INFO) << "OnDecryptInitDone,this:" << (void*)this;
+}
+
+void PipelineImpl::RendererWrapper::
+    CreateAndInitializeDecryptingMediaResource() {
+  DCHECK(media_task_runner_->RunsTasksInCurrentSequence());
+  DCHECK(cdm_context_);
+  decrypting_media_resource_ = std::make_unique<DecryptingMediaResource>(
+      demuxer_, cdm_context_, media_log_, media_task_runner_);
+
+  LOG(INFO) << "(" << static_cast<void*>(this) << __func__
+            << ") decrypting_media_resource_:"
+            << decrypting_media_resource_.get();
+
+  decrypting_media_resource_->Initialize(
+      base::BindOnce(&RendererWrapper::OnDecryptInitDone,
+                     weak_factory_.GetWeakPtr()),
+      base::BindRepeating(&RendererWrapper::OnWaiting,
+                          weak_factory_.GetWeakPtr()));
+}
+#endif
+
 void PipelineImpl::RendererWrapper::CreateRendererInternal(
     PipelineStatusCallback done_cb) {
   DVLOG(1) << __func__;
@@ -1249,7 +1285,7 @@ void PipelineImpl::RendererWrapper::CreateRenderer(
   DCHECK(state_ == kStarting || state_ == kResuming);
 
   if (HasEncryptedStream() && !cdm_context_) {
-    DVLOG(1) << __func__ << ": Has encrypted stream but CDM is not set.";
+    LOG(INFO) << __func__ << ": Has encrypted stream but CDM is not set.";
     create_renderer_done_cb_ = std::move(done_cb);
     OnWaiting(WaitingReason::kNoCdm);
     return;
@@ -1302,9 +1338,15 @@ void PipelineImpl::RendererWrapper::InitializeRenderer(
       break;
   }
 
-  if (cdm_context_)
+  if (cdm_context_) {
     shared_state_.renderer->SetCdm(cdm_context_, base::DoNothing());
 
+#if BUILDFLAG(IS_TIZEN_TV)
+    // Create decrypt media resource for eme
+    CreateAndInitializeDecryptingMediaResource();
+#endif
+  }
+
   if (latency_hint_)
     shared_state_.renderer->SetLatencyHint(latency_hint_);
 
@@ -1331,11 +1373,22 @@ void PipelineImpl::RendererWrapper::InitializeRenderer(
   // Initialize Renderer and report timeout UMA.
   std::string uma_name = "Media.InitializeRendererTimeout";
   base::UmaHistogramEnumeration(uma_name, CallbackTimeoutStatus::kCreate);
-  shared_state_.renderer->Initialize(
-      demuxer_, this,
-      WrapCallbackWithTimeoutHandler(
-          std::move(done_cb), /*timeout_delay=*/base::Seconds(10),
-          base::BindOnce(&OnCallbackTimeout, uma_name)));
+
+#if BUILDFLAG(IS_TIZEN_TV)
+  if ((MediaResource::Type::kStream == demuxer_->GetType()) && (cdm_context_)) {
+    if (!decrypting_media_resource_.get()) {
+      LOG(ERROR) << "decrypting_media_resource is not created yet";
+      return;
+    }
+    shared_state_.renderer->Initialize(decrypting_media_resource_.get(), this,
+                                       std::move(done_cb));
+  } else
+#endif
+    shared_state_.renderer->Initialize(
+        demuxer_, this,
+        WrapCallbackWithTimeoutHandler(
+            std::move(done_cb), /*timeout_delay=*/base::Seconds(10),
+            base::BindOnce(&OnCallbackTimeout, uma_name)));
 }
 
 void PipelineImpl::RendererWrapper::DestroyRenderer() {
index dee07ab..493b17e 100644 (file)
@@ -229,6 +229,19 @@ void DecryptingDemuxerStream::OnBufferReadFromDemuxerStream(
     return;
   }
 
+#if BUILDFLAG(IS_TIZEN_TV)
+  if (status == kNeedBuffer) {
+    LOG(ERROR) << __func__ << ": need buffer";
+    std::move(read_cb_).Run(status, {});
+    return;
+  }
+
+  if (!buffer) {
+    LOG(ERROR) << "buffer is null.";
+    return;
+  }
+#endif
+
   DCHECK_EQ(kOk, status);
 
   if (buffer->end_of_stream()) {
@@ -279,10 +292,19 @@ void DecryptingDemuxerStream::DecryptPendingBuffer() {
     switched_clear_to_encrypted_ = true;
   }
 
+#if BUILDFLAG(IS_TIZEN_TV)
+  decryptor_->DecryptTizen(
+      GetDecryptorStreamType(), pending_buffer_to_decrypt_,
+      base::BindPostTaskToCurrentDefault(
+          base::BindRepeating(&DecryptingDemuxerStream::OnBufferDecrypted,
+                              weak_factory_.GetWeakPtr())));
+#else
+
   decryptor_->Decrypt(GetDecryptorStreamType(), pending_buffer_to_decrypt_,
                       base::BindPostTaskToCurrentDefault(base::BindOnce(
                           &DecryptingDemuxerStream::OnBufferDecrypted,
                           weak_factory_.GetWeakPtr())));
+#endif
 }
 
 void DecryptingDemuxerStream::OnBufferDecrypted(
@@ -305,7 +327,9 @@ void DecryptingDemuxerStream::OnBufferDecrypted(
     return;
   }
 
+#if !BUILDFLAG(IS_TIZEN_TV)
   DCHECK_EQ(status == Decryptor::kSuccess, decrypted_buffer.get() != nullptr);
+#endif
 
   if (status == Decryptor::kError || status == Decryptor::kNeedMoreData) {
     DVLOG(2) << __func__ << ": Error with status " << status;
@@ -344,12 +368,23 @@ void DecryptingDemuxerStream::OnBufferDecrypted(
 
   DCHECK_EQ(status, Decryptor::kSuccess);
 
+#if BUILDFLAG(IS_TIZEN_TV)
+  // Returning status kSuccess and nullptr decryptor buffer is a way to
+  // inform that Decryptor interface cancelled decrypting.
+  if (decrypted_buffer.get() == nullptr) {
+    pending_buffer_to_decrypt_ = nullptr;
+    state_ = kIdle;
+    std::move(read_cb_).Run(kAborted, {});
+    return;
+  }
+#else
   // Copy the key frame flag and duration from the encrypted to decrypted
   // buffer.
   // TODO(crbug.com/1116263): Ensure all fields are copied by Decryptor.
   decrypted_buffer->set_is_key_frame(
       pending_buffer_to_decrypt_->is_key_frame());
   decrypted_buffer->set_duration(pending_buffer_to_decrypt_->duration());
+#endif
 
   pending_buffer_to_decrypt_ = nullptr;
   state_ = kIdle;
index 7647de0..02122ba 100644 (file)
@@ -97,6 +97,13 @@ TypeConverter<media::mojom::DecoderBufferPtr, media::DecoderBuffer>::Convert(
   mojo_buffer->front_discard = input.discard_padding().first;
   mojo_buffer->back_discard = input.discard_padding().second;
 
+#if BUILDFLAG(IS_TIZEN_TV)
+  mojo_buffer->tz_handle = base::checked_cast<uint32_t>(input.tz_handle());
+  mojo_buffer->tz_buffer_size = input.tz_buffer_size();
+  mojo_buffer->decryptor_handle =
+      base::checked_cast<uint64_t>(input.decryptor_handle());
+#endif
+
   mojo_buffer->side_data =
       media::mojom::DecoderBufferSideData::From(input.side_data());
 
@@ -132,6 +139,12 @@ TypeConverter<scoped_refptr<media::DecoderBuffer>,
   buffer->set_duration(input->duration);
   buffer->set_is_key_frame(input->is_key_frame);
 
+#if BUILDFLAG(IS_TIZEN_TV)
+  buffer->set_tz_handle(input->tz_handle);
+  buffer->set_tz_buffer_size(input->tz_buffer_size);
+  buffer->set_decryptor_handle(input->decryptor_handle);
+#endif
+
   if (input->decrypt_config) {
     buffer->set_decrypt_config(
         input->decrypt_config.To<std::unique_ptr<media::DecryptConfig>>());
index 57eccec..475edc8 100644 (file)
@@ -272,6 +272,13 @@ struct DecoderBuffer {
   mojo_base.mojom.TimeDelta front_discard;
   mojo_base.mojom.TimeDelta back_discard;
 
+  [EnableIf=is_tizen_tv]
+  uint32 tz_handle;
+  [EnableIf=is_tizen_tv]
+  uint32 tz_buffer_size;
+  [EnableIf=is_tizen_tv]
+  uint64 decryptor_handle;
+
   DecoderBufferSideData? side_data;
 };
 
index a1100cc..6cfff52 100644 (file)
@@ -20,6 +20,12 @@ Source1: content_shell.in
 %define _nodebug 1
 %endif
 
+%ifarch aarch64
+%define __drm_mapi_aarch_64 1
+%else
+%define __drm_mapi_aarch_64 0
+%endif
+
 %if 0%{?_nodebug}
 %global __debug_install_post %{nil}
 %global debug_package %{nil}
@@ -540,6 +546,9 @@ touch ./tizen_src/downloadable/ewk_api_wrapper_generator.py
 %if %{__enable_network_camera}
   "enable_network_camera=true" \
 %endif
+%if %{__drm_mapi_aarch_64} && "%{?tizen_profile_name}" == "tv" && %{tizen_version} >= 80
+  "drm_mapi_aarch_64=true" \
+%endif
 %endif  # _skip_gn
 
 ninja %{_smp_mflags} -C "%{OUTPUT_FOLDER}" \
index 374d189..b95bf9d 100644 (file)
@@ -112,4 +112,8 @@ config("tizen_feature_flags") {
   if (build_chrome) {
     defines += [ "BUILD_CHROME" ]
   }
+
+  if (drm_mapi_aarch_64) {
+    defines += [ "DRM_MAPI_AARCH_64" ]
+  }
 }
index 06cbe65..94c5421 100644 (file)
@@ -71,6 +71,8 @@ declare_args() {
   tizen_resource_manager = false
   enable_network_camera = false
   tizen_thread_booster_service = false
+
+  drm_mapi_aarch_64 = false
 }
 
 if (is_tizen && !build_chrome) {
diff --git a/tizen_src/chromium_impl/content/renderer/media/cdm/render_cdm_factory.cc b/tizen_src/chromium_impl/content/renderer/media/cdm/render_cdm_factory.cc
new file mode 100644 (file)
index 0000000..dc2a421
--- /dev/null
@@ -0,0 +1,44 @@
+// Copyright 2023 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "content/renderer/media/cdm/render_cdm_factory.h"
+
+#include "media/base/cdm_config.h"
+#include "media/base/cdm_promise.h"
+#include "media/base/content_decryption_module.h"
+#include "media/base/key_systems.h"
+#include "media/filters/ieme_drm_bridge.h"
+#include "third_party/widevine/cdm/widevine_cdm_common.h"
+
+namespace content {
+
+RenderCdmFactory::RenderCdmFactory() {}
+
+RenderCdmFactory::~RenderCdmFactory() {
+  DCHECK(thread_checker_.CalledOnValidThread());
+}
+
+void RenderCdmFactory::Create(
+    const media::CdmConfig& cdm_config,
+    const media::SessionMessageCB& session_message_cb,
+    const media::SessionClosedCB& session_closed_cb,
+    const media::SessionKeysChangeCB& session_keys_change_cb,
+    const media::SessionExpirationUpdateCB& session_expiration_update_cb,
+    media::CdmCreatedCB cdm_created_cb) {
+  DCHECK(thread_checker_.CalledOnValidThread());
+
+  // Currently privacy mode will be only used for Widevine. Other platform
+  // CDMs do not implement it. However, it is still possible to use Widevine
+  // without privacy mode.
+  const bool use_privacy_mode = cdm_config.key_system == kWidevineKeySystem;
+
+  // Tizen TV should use IEME for all key systems.
+  scoped_refptr<media::ContentDecryptionModule> cdm(media::CreateIEMEDrmBridge(
+      cdm_config.key_system, session_message_cb, session_closed_cb,
+      session_keys_change_cb, session_expiration_update_cb, use_privacy_mode));
+  base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
+      FROM_HERE, base::BindOnce(std::move(cdm_created_cb), cdm, ""));
+}
+
+}  // namespace content
\ No newline at end of file
diff --git a/tizen_src/chromium_impl/content/renderer/media/cdm/render_cdm_factory.h b/tizen_src/chromium_impl/content/renderer/media/cdm/render_cdm_factory.h
new file mode 100644 (file)
index 0000000..558ad42
--- /dev/null
@@ -0,0 +1,41 @@
+// Copyright 2023 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CONTENT_RENDERER_MEDIA_CDM_RENDER_CDM_FACTORY_H_
+#define CONTENT_RENDERER_MEDIA_CDM_RENDER_CDM_FACTORY_H_
+
+#include <memory>
+#include <string>
+
+#include "base/threading/thread_checker.h"
+#include "media/base/cdm_factory.h"
+
+namespace content {
+
+// CdmFactory implementation in content/renderer. This class is not thread safe
+// and should only be used on one thread.
+class RenderCdmFactory : public media::CdmFactory {
+ public:
+  RenderCdmFactory();
+  RenderCdmFactory(const RenderCdmFactory&) = delete;
+  RenderCdmFactory& operator=(const RenderCdmFactory&) = delete;
+
+  ~RenderCdmFactory() override;
+
+  // CdmFactory implementation
+  void Create(
+      const media::CdmConfig& cdm_config,
+      const media::SessionMessageCB& session_message_cb,
+      const media::SessionClosedCB& session_closed_cb,
+      const media::SessionKeysChangeCB& session_keys_change_cb,
+      const media::SessionExpirationUpdateCB& session_expiration_update_cb,
+      media::CdmCreatedCB cdm_created_cb) override;
+
+ private:
+  base::ThreadChecker thread_checker_;
+};
+
+}  // namespace content
+
+#endif  // CONTENT_RENDERER_MEDIA_CDM_RENDER_CDM_FACTORY_H_
\ No newline at end of file
index 1b19cae..de438fe 100644 (file)
@@ -14,6 +14,12 @@ external_content_renderer_efl_configs = [
   "//tizen_src/build:libtts",
 ]
 
+if (tizen_multimedia) {
+  external_content_renderer_efl_configs += [
+    "//tizen_src/build:drmdecrypt",
+  ]
+}
+
 ##############################################################################
 # Dependency
 ##############################################################################
@@ -35,3 +41,10 @@ if (tizen_multimedia) {
     "//tizen_src/chromium_impl/content/renderer/media/tizen/media_player_renderer_client_factory.h",
   ]
 }
+
+if (tizen_product_tv) {
+  external_content_renderer_efl_sources += [
+    "//tizen_src/chromium_impl/content/renderer/media/cdm/render_cdm_factory.cc",
+    "//tizen_src/chromium_impl/content/renderer/media/cdm/render_cdm_factory.h",
+  ]
+}
index 13cb234..6d62cd9 100644 (file)
@@ -104,6 +104,11 @@ esplusplayer_audio_mime_type ConvertToESPlusAudioMimeType(
     case media::AudioCodec::kAC3:
       audioMimeType = ESPLUSPLAYER_AUDIO_MIME_TYPE_AC3;
       break;
+#if BUILDFLAG(IS_TIZEN_TV) && TIZEN_VERSION_AT_LEAST(8, 0, 0)
+    case media::AudioCodec::kFLAC:
+      audioMimeType = ESPLUSPLAYER_AUDIO_MIME_TYPE_FLAC;
+      break;
+#endif
     default: {
       LOG(WARNING) << "Unknown codec :" << codec << ". Returning MP3.";
       audioMimeType = ESPLUSPLAYER_AUDIO_MIME_TYPE_MP3;
diff --git a/tizen_src/chromium_impl/media/filters/ieme_drm_bridge.cc b/tizen_src/chromium_impl/media/filters/ieme_drm_bridge.cc
new file mode 100644 (file)
index 0000000..25ae845
--- /dev/null
@@ -0,0 +1,794 @@
+// Copyright 2023 Samsung Electronics Inc. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "media/filters/ieme_drm_bridge.h"
+
+#include <drmdecrypt_api.h>
+#include <algorithm>
+#include <limits>
+
+#include "base/command_line.h"
+#include "base/functional/bind.h"
+#include "base/location.h"
+#include "base/logging.h"
+#include "base/memory/ptr_util.h"
+#include "base/strings/string_number_conversions.h"
+#include "content/public/common/content_switches.h"
+#include "media/base/decoder_buffer.h"
+#include "url/gurl.h"
+
+namespace media {
+
+namespace {
+
+static const base::TimeDelta kEmeDecryptRetryTime = base::Milliseconds(80);
+
+// Helper class to invoke free in smart pointers
+struct libc_free {
+  template <class Raw>
+  void operator()(Raw* ptr) {
+    ::free(ptr);
+  }
+};
+
+// Convert |session_type| to IEME SessionType used in session_create
+eme::SessionType ConvertSessionType(CdmSessionType session_type) {
+  switch (session_type) {
+    case CdmSessionType::kTemporary:
+      return eme::kTemporary;
+    case CdmSessionType::kPersistentLicense:
+      return eme::kPersistentLicense;
+    default:
+      LOG(ERROR) << "Not supported session type "
+                 << static_cast<int>(session_type) << ".";
+      return eme::kTemporary;
+  }
+}
+
+// Convert |message_type| from IEME message callback
+media::CdmMessageType ConvertMessageType(eme::MessageType message_type) {
+  switch (message_type) {
+    case eme::kLicenseRequest:
+      return CdmMessageType::LICENSE_REQUEST;
+    case eme::kLicenseRenewal:
+      return CdmMessageType::LICENSE_RENEWAL;
+    case eme::kLicenseRelease:
+      return CdmMessageType::LICENSE_RELEASE;
+    case eme::kIndividualizationRequest:
+      return CdmMessageType::LICENSE_REQUEST;
+    default:
+      LOG(ERROR) << "Not supported message type " << message_type << ".";
+      return CdmMessageType::LICENSE_REQUEST;
+  }
+}
+
+// Convert |init_data_type| to IEME InitDataType used in session_generateRequest
+eme::InitDataType ConvertInitDataType(media::EmeInitDataType init_data_type) {
+  switch (init_data_type) {
+    case media::EmeInitDataType::WEBM:
+      return eme::kWebM;
+    case media::EmeInitDataType::CENC:
+      return eme::kCenc;
+    case media::EmeInitDataType::KEYIDS:
+      return eme::kKeyIds;
+    default:
+      LOG(ERROR)
+          << "Not supported message init date type "
+          << static_cast<std::underlying_type<media::EmeInitDataType>::type>(
+                 init_data_type)
+          << ".";
+      // TODO(m.debski): Let's hope IEME checks this and returns reasonable
+      // error, is it ok to assume that? We can reject promise ourselves.
+      return std::numeric_limits<eme::InitDataType>::max();
+  }
+}
+
+CdmKeyInformation::KeyStatus ConvertKeyStatus(eme::KeyStatus key_status) {
+  switch (key_status) {
+    case eme::kUsable:
+      return CdmKeyInformation::USABLE;
+    case eme::kExpired:
+      return CdmKeyInformation::EXPIRED;
+    case eme::kOutputRestricted:
+      return CdmKeyInformation::OUTPUT_RESTRICTED;
+    case eme::kStatusPending:
+      // TODO(xhwang): This should probably be renamed to "PENDING".
+      return CdmKeyInformation::KEY_STATUS_PENDING;
+    case eme::kInternalError:
+      return CdmKeyInformation::INTERNAL_ERROR;
+    case eme::kReleased:
+      return CdmKeyInformation::RELEASED;
+    default:
+      LOG(ERROR) << "Not supported message key status " << key_status << ".";
+      return CdmKeyInformation::INTERNAL_ERROR;
+  }
+}
+
+// It should be used only to convert erros. Do not use it
+// for kSuccess status because CdmPromise::UNKNOWN_ERROR
+// will be returned.
+CdmPromise::Exception ConvertEmeError(eme::Status status) {
+  DCHECK(status != eme::kSuccess);
+  switch (status) {
+    case eme::kInvalidAccess:
+      return CdmPromise::Exception::TYPE_ERROR;
+    case eme::kNotSupported:
+      return CdmPromise::Exception::NOT_SUPPORTED_ERROR;
+    case eme::kInvalidState:
+      return CdmPromise::Exception::INVALID_STATE_ERROR;
+    case eme::kQuotaExceeded:
+      return CdmPromise::Exception::QUOTA_EXCEEDED_ERROR;
+    default:
+      return CdmPromise::Exception::TYPE_ERROR;
+  }
+}
+
+std::string ConvertEmeErrorToString(eme::Status status) {
+  DCHECK(status != eme::kSuccess);
+  switch (status) {
+    case eme::kInvalidAccess:
+      return "Invalid access error";
+    case eme::kNotSupported:
+      return "Operation not supported";
+    case eme::kInvalidState:
+      return "Invalid state";
+    case eme::kQuotaExceeded:
+      return "Quota was exceed";
+    default:
+      return "";
+  }
+}
+
+msd_cipher_algorithm ConvertEncryptionScheme(
+    EncryptionScheme encryption_scheme) {
+  switch (encryption_scheme) {
+    case EncryptionScheme::kCenc:
+      return MSD_AES128_CTR;
+    case EncryptionScheme::kCbcs:
+      return MSD_AES128_CBC;
+    default:
+      LOG(WARNING) << "Unknown encryption scheme";
+      return MSD_AES128_CTR;
+  }
+}
+
+void RejectCdmPromise(CdmPromise& promise,
+                      eme::Status error,
+                      const std::string& message) {
+  DCHECK(error != eme::kSuccess);
+  std::stringstream ss;
+  ss << message << ". " << ConvertEmeErrorToString(error) << ".";
+  promise.reject(ConvertEmeError(error), error, ss.str());
+}
+
+std::string CreateStringFromBinaryData(const std::vector<uint8_t>& data) {
+  // This way string can contain null characters
+  return std::string(reinterpret_cast<const char*>(data.data()), data.size());
+}
+
+std::vector<uint8_t> CreateBinaryDataFromString(const std::string& data) {
+  const auto data_ptr = reinterpret_cast<const uint8_t*>(data.data());
+  const auto data_size = sizeof(*data.data()) * data.size();
+  CHECK(data_size >= 0);
+  return std::vector<uint8_t>(data_ptr, data_ptr + data_size);
+}
+
+}  // namespace
+
+// static
+bool IEMEDrmBridge::IsKeySystemSupported(const std::string& key_system) {
+  CHECK(!key_system.empty());
+  return eme::IEME::isKeySystemSupported(key_system) == eme::kSupported;
+}
+
+IEMEDrmBridge::IEMEDrmBridge(
+    const std::string& key_system,
+    const SessionMessageCB& session_message_cb,
+    const SessionClosedCB& session_closed_cb,
+    const SessionKeysChangeCB& session_keys_change_cb,
+    const SessionExpirationUpdateCB& session_expiration_update_cb,
+    bool privacy_mode)
+    : key_system_(key_system),
+      session_message_cb_(session_message_cb),
+      session_closed_cb_(session_closed_cb),
+      session_keys_change_cb_(session_keys_change_cb),
+      session_expiration_update_cb_(session_expiration_update_cb),
+      cdm_promise_adapter_(new CdmPromiseAdapter()),
+      single_process_mode_(false),
+      task_runner_(base::SingleThreadTaskRunner::GetCurrentDefault()),
+      decrypt_buffer_full_strategy_(
+          DecryptBufferFullStrategy::kRetryDecryption),
+      weak_factory_(this) {
+  LOG(INFO) << "Constructing DRM for " << key_system_;
+  single_process_mode_ = base::CommandLine::ForCurrentProcess()->HasSwitch(
+      switches::kSingleProcess);
+  LOG(INFO) << "single process mode:" << std::boolalpha << single_process_mode_;
+  // TODO(mdebski): what is privacy_mode?
+  cdm_.reset(eme::IEME::create(this, key_system_, false));
+  CHECK(cdm_.get());
+}
+
+IEMEDrmBridge::~IEMEDrmBridge() {
+  LOG(INFO) << "Destroying IEMEDrmBridge";
+
+  // Destroy our CDM so no callbacks are called from this point onwards.
+  cdm_.reset();
+  cdm_promise_adapter_.reset();
+}
+
+// static
+ScopedIEMEDrmBridgePtr IEMEDrmBridge::Create(
+    const std::string& key_system,
+    const SessionMessageCB& session_message_cb,
+    const SessionClosedCB& session_closed_cb,
+    const SessionKeysChangeCB& session_keys_change_cb,
+    const SessionExpirationUpdateCB& session_expiration_update_cb,
+    bool privacy_mode) {
+  LOG(INFO) << "Creating DRM  bridge for key system: " << key_system;
+  ScopedIEMEDrmBridgePtr ieme_drm_bridge;
+
+  if (IsKeySystemSupported(key_system)) {
+    ieme_drm_bridge.reset(new IEMEDrmBridge(
+        key_system, session_message_cb, session_closed_cb,
+        session_keys_change_cb, session_expiration_update_cb, privacy_mode));
+
+    if (ieme_drm_bridge->cdm_ == nullptr)
+      ieme_drm_bridge.reset();
+  }
+
+  return ieme_drm_bridge;
+}
+
+// static
+ScopedIEMEDrmBridgePtr IEMEDrmBridge::CreateWithoutSessionSupport(
+    const std::string& key_system) {
+  return IEMEDrmBridge::Create(key_system, SessionMessageCB(),
+                               SessionClosedCB(), SessionKeysChangeCB(),
+                               SessionExpirationUpdateCB(), false);
+}
+
+void IEMEDrmBridge::SetServerCertificate(
+    const std::vector<uint8_t>& certificate,
+    std::unique_ptr<media::SimpleCdmPromise> promise) {
+  LOG(INFO) << "Setting server certificate";
+  CHECK(!certificate.empty());
+
+  const auto result_status =
+      cdm_->setServerCertificate(CreateStringFromBinaryData(certificate));
+  if (eme::kSuccess == result_status) {
+    promise->resolve();
+  } else {
+    LOG(ERROR) << "Setting server certificate failed with status: "
+               << result_status;
+    promise->reject(CdmPromise::Exception::TYPE_ERROR, 0,
+                    "Set server certificate failed.");
+  }
+}
+
+void IEMEDrmBridge::CreateSessionAndGenerateRequest(
+    CdmSessionType session_type,
+    media::EmeInitDataType init_data_type,
+    const std::vector<uint8_t>& init_data,
+    std::unique_ptr<media::NewSessionCdmPromise> promise) {
+  LOG(INFO) << "Creating session session_type: "
+            << static_cast<int>(session_type) << " init_data_type: "
+            << static_cast<std::underlying_type<media::EmeInitDataType>::type>(
+                   init_data_type);
+
+  std::string session_id;
+  const auto create_result_status =
+      cdm_->session_create(ConvertSessionType(session_type), &session_id);
+  if (eme::kSuccess != create_result_status) {
+    LOG(ERROR) << "Creating session failed with status: "
+               << create_result_status;
+    RejectCdmPromise(*promise, create_result_status, "Session creation failed");
+    return;
+  } else if (session_id.empty()) {
+    LOG(ERROR) << "Session empty";
+    promise->reject(CdmPromise::Exception::TYPE_ERROR, create_result_status,
+                    "Returned session Id is empty.");
+    return;
+  }
+
+  LOG(INFO) << "session_create success:" << session_id;
+  const auto generate_result_status = cdm_->session_generateRequest(
+      session_id, ConvertInitDataType(init_data_type),
+      CreateStringFromBinaryData(init_data));
+  if (eme::kSuccess != generate_result_status) {
+    LOG(ERROR) << "IEME::session_generateRequest failed with status: "
+               << generate_result_status;
+    RejectCdmPromise(*promise, generate_result_status,
+                     "Request generation failed");
+    return;
+  }
+
+  LOG(INFO) << "session_generateRequest success:" << session_id;
+  promise->resolve(session_id);
+}
+
+void IEMEDrmBridge::LoadSession(
+    CdmSessionType session_type,
+    const std::string& session_id,
+    std::unique_ptr<media::NewSessionCdmPromise> promise) {
+  LOG(INFO) << "Loading session id: " << session_id;
+
+  CHECK(session_type != CdmSessionType::kTemporary);
+
+  const auto load_result_status = cdm_->session_load(session_id);
+  if (eme::kSuccess != load_result_status) {
+    LOG(ERROR) << "Loading session failed with status: " << load_result_status;
+    RejectCdmPromise(*promise, load_result_status, "Loading session failed");
+    return;
+  }
+
+  promise->resolve(session_id);
+  onKeyStatusesChange(session_id);
+}
+
+void IEMEDrmBridge::UpdateSession(
+    const std::string& session_id,
+    const std::vector<uint8_t>& response,
+    std::unique_ptr<media::SimpleCdmPromise> promise) {
+  LOG(INFO) << "Updating session id: " << session_id;
+
+  const auto update_result_status =
+      cdm_->session_update(session_id, CreateStringFromBinaryData(response));
+  if (eme::kSuccess != update_result_status) {
+    LOG(ERROR) << "Updating session failed with status: "
+               << update_result_status;
+    RejectCdmPromise(*promise, update_result_status, "Updating session failed");
+    return;
+  }
+
+  promise->resolve();
+  onKeyStatusesChange(session_id);
+}
+
+void IEMEDrmBridge::CloseSession(
+    const std::string& session_id,
+    std::unique_ptr<media::SimpleCdmPromise> promise) {
+  LOG(INFO) << "Closing session id: " << session_id;
+
+  const auto close_result_status = cdm_->session_close(session_id);
+  if (eme::kSuccess != close_result_status) {
+    LOG(ERROR) << "Closing failed with status: " << close_result_status;
+    RejectCdmPromise(*promise, close_result_status, "Closing session failed.");
+    return;
+  }
+
+  session_key_statuses_map_.erase(session_id);
+  last_expiration_times_map_.erase(session_id);
+
+  promise->resolve();
+  task_runner_->PostTask(FROM_HERE,
+                         base::BindRepeating(session_closed_cb_, session_id,
+                                             CdmSessionClosedReason::kClose));
+  onKeyStatusesChange(session_id);
+}
+
+void IEMEDrmBridge::RemoveSession(
+    const std::string& session_id,
+    std::unique_ptr<media::SimpleCdmPromise> promise) {
+  LOG(INFO) << "Removing session id: " << session_id;
+
+  const auto remove_result_status = cdm_->session_remove(session_id);
+  if (eme::kSuccess != remove_result_status) {
+    LOG(ERROR) << "Removing session id: " << session_id
+               << " failed with status: " << remove_result_status;
+    RejectCdmPromise(*promise, remove_result_status,
+                     "Removing session failed.");
+    return;
+  }
+
+  CHECK(session_promise_map_.find(session_id) == session_promise_map_.end());
+  session_promise_map_[session_id] =
+      cdm_promise_adapter_->SavePromise(std::move(promise));
+  onKeyStatusesChange(session_id);
+}
+
+CdmContext* IEMEDrmBridge::GetCdmContext() {
+  return this;
+}
+
+eme::eme_decryptor_t IEMEDrmBridge::GetDecryptorHandle() {
+  return cdm_->getDecryptor("");
+}
+
+const std::string& IEMEDrmBridge::GetKeySystem() const {
+  return key_system_;
+}
+
+Decryptor* IEMEDrmBridge::GetDecryptor() {
+  return this;
+}
+
+absl::optional<base::UnguessableToken> IEMEDrmBridge::GetCdmId() const {
+  // we want to use Decryptor API
+  // return CdmContext::kInvalidCdmId;
+  return absl::nullopt;
+}
+
+std::unique_ptr<CallbackRegistration> IEMEDrmBridge::RegisterEventCB(
+    EventCB event_cb) {
+  return event_callbacks_.Register(std::move(event_cb));
+}
+
+#if BUILDFLAG(IS_TIZEN_TV) && TIZEN_VERSION_AT_LEAST(6, 0, 0)
+bool IEMEDrmBridge::IsKeyReady(const std::string& kid) {
+  for (auto const& session_key : session_key_statuses_map_) {
+    KayStatusesMap key_status = session_key.second;
+    auto iter = key_status.find(kid);
+    if (iter == key_status.end())
+      continue;
+    if (iter->second == CdmKeyInformation::USABLE)
+      return true;
+  }
+  return false;
+}
+#endif
+
+void IEMEDrmBridge::DecryptInChromium(
+    StreamType stream_type,
+    const scoped_refptr<DecoderBuffer> encrypted,
+    DecryptCBTizen decrypt_cb) {
+  auto decrypt_conf = encrypted->decrypt_config();
+  scoped_refptr<DecoderBuffer> decrypted = encrypted;
+  Decryptor::Status status = Decryptor::kError;
+
+  MSD_SUBSAMPLE_INFO subsamples_info;
+  std::vector<MSD_SUBSAMPLE_INFO> subsamples;
+  subsamples.reserve(decrypt_conf->subsamples().size());
+  for (const auto& subsample : decrypt_conf->subsamples()) {
+    subsamples_info.uBytesOfClearData = subsample.clear_bytes;
+    subsamples_info.uBytesOfEncryptedData = subsample.cypher_bytes;
+    subsamples.emplace_back(subsamples_info);
+  }
+
+  MSD_FMP4_DATA subsamples_data;
+  subsamples_data.uSubSampleCount = subsamples.size();
+  subsamples_data.pSubSampleInfo = subsamples.data();
+
+  msd_cipher_param_s params;
+  params.algorithm = ConvertEncryptionScheme(decrypt_conf->encryption_scheme());
+  params.format = MSD_FORMAT_FMP4;
+  params.phase = MSD_PHASE_NONE;
+  params.buseoutbuf = false;
+  params.pkid = reinterpret_cast<unsigned char*>(
+      const_cast<char*>(decrypt_conf->key_id().data()));
+  params.ukidlen = decrypt_conf->key_id().size();
+  params.pdata = reinterpret_cast<unsigned char*>(
+      const_cast<unsigned char*>(encrypted->data()));
+  params.udatalen = encrypted->data_size();
+  params.poutbuf = nullptr;
+  params.uoutbuflen = 0;
+  params.piv = reinterpret_cast<unsigned char*>(
+      const_cast<char*>(decrypt_conf->iv().data()));
+  params.uivlen = decrypt_conf->iv().size();
+  params.psubdata = &subsamples_data;
+  params.psplitoffsets = nullptr;
+
+  /*
+    CTR encryption :
+    CENS : pattern->crypt_byte_block() != 0 ||  pattern->skip_byte_block() != 0
+    CENC : pattern->crypt_byte_block() == 0 &&  pattern->skip_byte_block() == 0
+    DRM not support CENS.
+  */
+  if (decrypt_conf->HasPattern()) {
+    const auto& pattern = decrypt_conf->encryption_pattern();
+    params.busepattern = true;
+    params.uCrypt_byte_block = pattern->crypt_byte_block();
+    params.uSkip_byte_block = pattern->skip_byte_block();
+  }
+
+  msd_cipher_param_s* params_ptr = &params;
+
+  // This one was allocated with malloc so should be deallocated with free
+  handle_and_size_s* decrypted_params_ptr = nullptr;
+
+  auto ret = eme_decryptarray(GetDecryptorHandle(), &params_ptr, 1, nullptr, 0,
+                              &decrypted_params_ptr);
+  std::unique_ptr<handle_and_size_s, libc_free> scoped_decrypted_params_ptr{
+      decrypted_params_ptr};
+  if (ret == E_SUCCESS) {
+    decrypted = DecoderBuffer::CreateTzHandleBuffer(
+        decrypted_params_ptr->handle, decrypted_params_ptr->size);
+    decrypted->set_timestamp(encrypted->timestamp());
+    decrypted->set_duration(encrypted->duration());
+    status = Decryptor::kSuccess;
+  } else if (ret == E_NEED_KEY) {
+    LOG(WARNING) << "eme_decryptarray() failed no decryption key";
+    status = Decryptor::kNoKey;
+  }
+#if BUILDFLAG(IS_TIZEN_TV)
+  else if (ret == E_DECRYPT_BUFFER_FULL &&
+           decrypt_buffer_full_strategy_ ==
+               DecryptBufferFullStrategy::kAbortDecryption) {
+    status = Decryptor::kBufferFull;
+  }
+#endif
+  else if (ret == E_DECRYPT_BUFFER_FULL) {
+    LOG(WARNING) << "eme_decryptarray() TZ buffer full, stream type: "
+                 << (stream_type == Decryptor::kAudio ? "AUDIO" : "VIDEO");
+    decrypt_cb_map_[stream_type].last_decrypt_cb_ = decrypt_cb;
+    decrypt_cb_map_[stream_type].pending_decrypt_cb_.Reset(
+        base::BindOnce(&IEMEDrmBridge::DecryptTizen, weak_factory_.GetWeakPtr(),
+                       stream_type, encrypted, decrypt_cb));
+    base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
+        FROM_HERE, decrypt_cb_map_[stream_type].pending_decrypt_cb_.callback(),
+        kEmeDecryptRetryTime);
+    return;
+  } else {
+    LOG(ERROR) << "eme_decryptarray() failed with err code: " << ret;
+    status = Decryptor::kError;
+  }
+
+  decrypt_cb.Run(status, decrypted);
+  decrypt_cb_map_.erase(stream_type);
+}
+
+void IEMEDrmBridge::Decrypt(StreamType stream_type,
+                            const scoped_refptr<DecoderBuffer> encrypted,
+                            DecryptCB decrypt_cb) {}
+
+void IEMEDrmBridge::DecryptTizen(StreamType stream_type,
+                                 const scoped_refptr<DecoderBuffer> encrypted,
+                                 DecryptCBTizen decrypt_cb) {
+  auto decrypt_conf = encrypted->decrypt_config();
+  scoped_refptr<DecoderBuffer> decrypted = encrypted;
+
+  // |Decrypt()| is asynchronously called so |cdm_| can be nullptr.
+  if (!cdm_) {
+    LOG(ERROR) << "cdm is nullptr";
+    decrypt_cb.Run(Decryptor::kNoKey, decrypted);
+    return;
+  }
+
+  if (!decrypt_conf) {
+    LOG(ERROR) << "decrypt_conf should not be nullptr";
+    decrypt_cb.Run(Decryptor::kError, decrypted);
+    decrypt_cb_map_.erase(stream_type);
+    return;
+  }
+
+  if (decrypt_conf && decrypt_conf->iv().size() <= 0) {
+    LOG(ERROR) << "iv size <= 0";
+    decrypt_cb.Run(Decryptor::kSuccess, decrypted);
+    decrypt_cb_map_.erase(stream_type);
+    return;
+  }
+
+#if BUILDFLAG(IS_TIZEN_TV) && TIZEN_VERSION_AT_LEAST(6, 0, 0)
+  if (single_process_mode_) {
+    if (!IsKeyReady(decrypt_conf->key_id())) {
+      LOG(ERROR) << base::HexEncode(decrypt_conf->key_id().data(),
+                                    decrypt_conf->key_id().size())
+                 << " is not ready yet";
+      decrypt_cb.Run(Decryptor::kNoKey, encrypted);
+      return;
+    }
+
+    if (!GetDecryptorHandle()) {
+      LOG(ERROR) << "decryptor handle is not ready yet";
+      decrypt_cb.Run(Decryptor::kNoKey, encrypted);
+      return;
+    }
+
+    // esplusplayer will decrypt the frames
+    encrypted->set_decryptor_handle(GetDecryptorHandle());
+    decrypt_cb.Run(Decryptor::kSuccess, encrypted);
+    decrypt_cb_map_.erase(stream_type);
+  } else
+#endif
+    DecryptInChromium(stream_type, encrypted, decrypt_cb);
+}
+
+void IEMEDrmBridge::CancelDecrypt(StreamType stream_type) {
+  // Decrypt() calls the DecryptCB synchronously, but if DRM return
+  // E_DECRYPT_BUFFER_FULL, IEME will repeat decryption. During
+  // CancelDecrypt we want to break this loop and finish decryption.
+  if (decrypt_cb_map_.count(stream_type) == 1) {
+    decrypt_cb_map_[stream_type].pending_decrypt_cb_.Cancel();
+    decrypt_cb_map_[stream_type].last_decrypt_cb_.Run(Decryptor::kSuccess,
+                                                      nullptr);
+    decrypt_cb_map_.erase(stream_type);
+  }
+}
+
+// We problably won't use that
+void IEMEDrmBridge::InitializeAudioDecoder(const AudioDecoderConfig& config,
+                                           DecoderInitCB init_cb) {
+  // IEMEDrmBridge does not support audio decoding.
+  std::move(init_cb).Run(false);
+}
+
+void IEMEDrmBridge::InitializeVideoDecoder(const VideoDecoderConfig& config,
+                                           DecoderInitCB init_cb) {
+  // IEMEDrmBridge does not support video decoding.
+  std::move(init_cb).Run(false);
+}
+
+void IEMEDrmBridge::DecryptAndDecodeAudio(
+    const scoped_refptr<DecoderBuffer> encrypted,
+    AudioDecodeCB audio_decode_cb) {
+  NOTREACHED() << "IEMEDrmBridge does not support audio decoding";
+}
+
+void IEMEDrmBridge::DecryptAndDecodeVideo(
+    const scoped_refptr<DecoderBuffer> encrypted,
+    VideoDecodeCB video_decode_cb) {
+  NOTREACHED() << "IEMEDrmBridge does not support video decoding";
+}
+
+void IEMEDrmBridge::ResetDecoder(StreamType stream_type) {
+  NOTREACHED() << "IEMEDrmBridge does not support audio/video decoding";
+}
+
+void IEMEDrmBridge::DeinitializeDecoder(StreamType stream_type) {
+  // IEMEDrmBridge does not support audio/video decoding, but since this can be
+  // called any time after InitializeAudioDecoder/InitializeVideoDecoder,
+  // nothing to be done here.
+}
+
+void IEMEDrmBridge::IEMEDrmBridge::onMessage(const std::string& session_id,
+                                             eme::MessageType message_type,
+                                             const std::string& message) {
+  LOG(INFO) << "onMessage, session: " << session_id << " type: " << message_type
+            << " message: " << message;
+
+  if (eme::kLicenseAlreadyDone == message_type)
+    return;
+
+  task_runner_->PostTask(FROM_HERE,
+                         base::BindOnce(session_message_cb_, session_id,
+                                        ConvertMessageType(message_type),
+                                        CreateBinaryDataFromString(message)));
+}
+
+void IEMEDrmBridge::onKeyStatusesChange(const std::string& session_id) {
+  task_runner_->PostTask(
+      FROM_HERE, base::BindOnce(&IEMEDrmBridge::ChangeKeyStatuses,
+                                weak_factory_.GetWeakPtr(), session_id));
+}
+
+void IEMEDrmBridge::ChangeKeyStatuses(const std::string& session_id) {
+  LOG(INFO) << "ChangeKeyStatuses session: " << session_id;
+  eme::KeyStatusMap key_statuses;
+  const auto key_status_result_status =
+      cdm_->session_getKeyStatuses(session_id, &key_statuses);
+  if (key_status_result_status != eme::kSuccess) {
+    LOG(ERROR) << "Could not retrieve key statuses for session id: "
+               << session_id << " status: " << key_status_result_status;
+    return;
+  }
+  UpdateKeyStatuses(session_id, key_statuses);
+  UpdateExpirationTime(session_id);
+}
+
+void IEMEDrmBridge::UpdateKeyStatuses(const std::string& session_id,
+                                      const eme::KeyStatusMap& key_statuses) {
+  bool has_additional_usable_key = false;
+  bool key_status_change = false;
+
+  CdmKeysInfo cdm_keys_info;
+  KayStatusesMap old_key_statuses_map;
+  auto& current_key_statuses_map = session_key_statuses_map_[session_id];
+  std::swap(old_key_statuses_map, current_key_statuses_map);
+
+  for (const auto& key : key_statuses) {
+    const auto key_id = CreateBinaryDataFromString(key.first);
+    CHECK(!key_id.empty());
+
+    CdmKeyInformation::KeyStatus key_status = ConvertKeyStatus(key.second);
+
+    const auto it = old_key_statuses_map.find(key.first);
+    if ((it == old_key_statuses_map.end() || it->second != key_status) &&
+        // EME spec claims that onKeyStatusChange event should be fired for
+        // pending key status, but it is suppressed due to problems repoted
+        // by major content providers.
+        key_status != CdmKeyInformation::KEY_STATUS_PENDING) {
+      key_status_change = true;
+      if (key_status == CdmKeyInformation::USABLE) {
+        has_additional_usable_key = true;
+      }
+    }
+    if (it != old_key_statuses_map.end()) {
+      old_key_statuses_map.erase(it);
+    }
+
+    current_key_statuses_map[key.first] = key_status;
+
+    LOG(INFO) << "Key status: " << base::HexEncode(key_id.data(), key_id.size())
+              << ", " << key_status;
+    cdm_keys_info.push_back(std::make_unique<CdmKeyInformation>(
+        key_id, key_status, 0 /* there is no key system code */));
+  }
+
+  event_callbacks_.Notify(Event::kHasAdditionalUsableKey);
+
+  // Session keys change callback will be fired when:
+  // 1. New keys were added
+  // 2. Keys were removed
+  // 3. Keys statuses were changed
+  if (key_status_change || !old_key_statuses_map.empty()) {
+    session_keys_change_cb_.Run(session_id, has_additional_usable_key,
+                                std::move(cdm_keys_info));
+  }
+}
+
+void IEMEDrmBridge::UpdateExpirationTime(const std::string& session_id) {
+  LOG(INFO) << "Updating expiration time session: " << session_id;
+
+  int64_t expiration_time;
+  const auto get_expiration_result_status =
+      cdm_->session_getExpiration(session_id, &expiration_time);
+  if (eme::kSuccess != get_expiration_result_status) {
+    LOG(ERROR) << "Updating expiration time for session id: " << session_id
+               << " failed with status: " << get_expiration_result_status;
+    return;
+  }
+
+  LOG(INFO) << "Expiration time: " << expiration_time << " ms";
+
+  const auto last_expiration_it = last_expiration_times_map_.find(session_id);
+
+  if (last_expiration_times_map_.end() == last_expiration_it ||
+      expiration_time != last_expiration_it->second) {
+    last_expiration_times_map_[session_id] = expiration_time;
+    NotifySessionExpirationUpdate(session_id, expiration_time);
+  }
+}
+
+void IEMEDrmBridge::onRemoveComplete(const std::string& session_id) {
+  task_runner_->PostTask(
+      FROM_HERE, base::BindOnce(&IEMEDrmBridge::RemoveComplete,
+                                weak_factory_.GetWeakPtr(), session_id));
+}
+
+void IEMEDrmBridge::RemoveComplete(const std::string& session_id) {
+  LOG(INFO) << "RemoveComplete session: " << session_id;
+  const auto promise_it = session_promise_map_.find(session_id);
+  CHECK(promise_it != session_promise_map_.end());
+
+  if (promise_it != session_promise_map_.end()) {
+    IEMEDrmBridge::ResolvePromise(promise_it->second);
+    session_promise_map_.erase(promise_it);
+  }
+}
+
+void IEMEDrmBridge::ResolvePromise(uint32_t promise_id) {
+  cdm_promise_adapter_->ResolvePromise(promise_id);
+}
+
+void IEMEDrmBridge::SetDecryptStrategy(DecryptBufferFullStrategy strategy) {
+  decrypt_buffer_full_strategy_ = strategy;
+}
+
+// From IEME doc: |expiry_time_ms| is the time, in milliseconds since 1970 UTC,
+// after which the key(s) in the session will no longer be usable to decrypt
+// media data, or -1 if no such time exists.
+void IEMEDrmBridge::NotifySessionExpirationUpdate(const std::string& session_id,
+                                                  int64_t expiry_time_ms) {
+  if (expiry_time_ms < 0) {
+    expiry_time_ms = 0;
+  }
+
+  session_expiration_update_cb_.Run(
+      session_id,
+      base::Time::FromSecondsSinceUnixEpoch(expiry_time_ms / 1000.0));
+}
+
+ContentDecryptionModule* CreateIEMEDrmBridge(
+    const std::string& key_system,
+    const SessionMessageCB& session_message_cb,
+    const SessionClosedCB& session_closed_cb,
+    const SessionKeysChangeCB& session_keys_change_cb,
+    const SessionExpirationUpdateCB& session_expiration_update_cb,
+    bool privacy_mode) {
+  return IEMEDrmBridge::Create(key_system, session_message_cb,
+                               session_closed_cb, session_keys_change_cb,
+                               session_expiration_update_cb, privacy_mode)
+      .release();
+}
+
+}  // namespace media
diff --git a/tizen_src/chromium_impl/media/filters/ieme_drm_bridge.h b/tizen_src/chromium_impl/media/filters/ieme_drm_bridge.h
new file mode 100644 (file)
index 0000000..69a3bd9
--- /dev/null
@@ -0,0 +1,221 @@
+// Copyright 2023 Samsung Electronics Inc. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MEDIA_BASE_ANDROID_IEME_DRM_BRIDGE_H_
+#define MEDIA_BASE_ANDROID_IEME_DRM_BRIDGE_H_
+
+#include <emeCDM/IEME.h>
+#include <string>
+#include <unordered_map>
+#include <vector>
+
+#include "base/cancelable_callback.h"
+#include "base/functional/callback.h"
+#include "base/memory/weak_ptr.h"
+#include "base/task/single_thread_task_runner.h"
+#include "build/tizen_version.h"
+#include "media/base/callback_registry.h"
+#include "media/base/cdm_context.h"
+#include "media/base/cdm_key_information.h"
+#include "media/base/cdm_promise_adapter.h"
+#include "media/base/content_decryption_module.h"
+#include "media/base/decryptor.h"
+#include "media/base/media_export.h"
+
+class GURL;
+
+namespace media {
+
+class IEMEDrmBridge;
+class MediaPlayerManager;
+
+using ScopedIEMEDrmBridgePtr = std::unique_ptr<IEMEDrmBridge>;
+
+// This class provides DRM services for tizen capi EME implementation.
+class MEDIA_EXPORT IEMEDrmBridge : public ContentDecryptionModule,
+                                   public CdmContext,
+                                   public Decryptor,
+                                   public eme::IEventListener {
+ public:
+  ~IEMEDrmBridge() override;
+
+  IEMEDrmBridge(const IEMEDrmBridge&) = delete;
+  IEMEDrmBridge& operator=(const IEMEDrmBridge&) = delete;
+
+  // Checks whether |key_system| is supported.
+  static bool IsKeySystemSupported(const std::string& key_system);
+
+  // Returns a MediaDrmBridge instance if |key_system| is supported, or a NULL
+  // pointer otherwise.
+  // TODO(xhwang): Is it okay not to update session expiration info?
+  static ScopedIEMEDrmBridgePtr Create(
+      const std::string& key_system,
+      const SessionMessageCB& session_message_cb,
+      const SessionClosedCB& session_closed_cb,
+      const SessionKeysChangeCB& session_keys_change_cb,
+      const SessionExpirationUpdateCB& session_expiration_update_cb,
+      bool privacy_mode);
+
+  // Returns a MediaDrmBridge instance if |key_system| is supported, or a NULL
+  // otherwise. No session callbacks are provided. This is used when we need to
+  // use MediaDrmBridge without creating any sessions.
+  static ScopedIEMEDrmBridgePtr CreateWithoutSessionSupport(
+      const std::string& key_system);
+
+  // MediaKeys (via BrowserCdm) implementation.
+  void SetServerCertificate(
+      const std::vector<uint8_t>& certificate,
+      std::unique_ptr<media::SimpleCdmPromise> promise) override;
+  void CreateSessionAndGenerateRequest(
+      CdmSessionType session_type,
+      media::EmeInitDataType init_data_type,
+      const std::vector<uint8_t>& init_data,
+      std::unique_ptr<media::NewSessionCdmPromise> promise) override;
+  void LoadSession(
+      CdmSessionType session_type,
+      const std::string& js_session_id,
+      std::unique_ptr<media::NewSessionCdmPromise> promise) override;
+  void UpdateSession(const std::string& js_session_id,
+                     const std::vector<uint8_t>& response,
+                     std::unique_ptr<media::SimpleCdmPromise> promise) override;
+  void CloseSession(const std::string& js_session_id,
+                    std::unique_ptr<media::SimpleCdmPromise> promise) override;
+  void RemoveSession(const std::string& js_session_id,
+                     std::unique_ptr<media::SimpleCdmPromise> promise) override;
+  CdmContext* GetCdmContext() override;
+
+  // Get handle for this IEME instance
+  eme::eme_decryptor_t GetDecryptorHandle() override;
+
+  // Return key system which it was configured to
+  const std::string& GetKeySystem() const;
+
+  // CdmContext interface
+  Decryptor* GetDecryptor() override;
+
+  // TODO(m.debski): We could make it return the same type as
+  // IEME::getDecryptor() and use that from Browser side MediaPlayer
+  // to create drm_info.
+  absl::optional<base::UnguessableToken> GetCdmId() const override;
+
+  // TODO(m.debski):
+  // Decryptor interface
+  std::unique_ptr<CallbackRegistration> RegisterEventCB(EventCB event_cb);
+
+  void Decrypt(StreamType stream_type,
+               scoped_refptr<DecoderBuffer> encrypted,
+               DecryptCB decrypt_cb) override;
+  void DecryptTizen(StreamType stream_type,
+                    scoped_refptr<DecoderBuffer> encrypted,
+                    DecryptCBTizen decrypt_cb) override;
+  void CancelDecrypt(StreamType stream_type) override;
+  // We problably won't use that
+  void InitializeAudioDecoder(const AudioDecoderConfig& config,
+                              DecoderInitCB init_cb) override;
+  void InitializeVideoDecoder(const VideoDecoderConfig& config,
+                              DecoderInitCB init_cb) override;
+  void DecryptAndDecodeAudio(const scoped_refptr<DecoderBuffer> encrypted,
+                             AudioDecodeCB audio_decode_cb) override;
+  void DecryptAndDecodeVideo(const scoped_refptr<DecoderBuffer> encrypted,
+                             VideoDecodeCB video_decode_cb) override;
+  void ResetDecoder(StreamType stream_type) override;
+  void DeinitializeDecoder(StreamType stream_type) override;
+  void SetDecryptStrategy(DecryptBufferFullStrategy) override;
+
+  // eme::IEventListener implementation.
+  void onMessage(const std::string& ieme_session_id,
+                 eme::MessageType message_type,
+                 const std::string& message) override;
+  void onKeyStatusesChange(const std::string& ieme_session_id) override;
+  void onRemoveComplete(const std::string& ieme_session_id) override;
+
+ private:
+  IEMEDrmBridge(const std::string& key_system,
+                const SessionMessageCB& session_message_cb,
+                const SessionClosedCB& session_closed_cb,
+                const SessionKeysChangeCB& session_keys_change_cb,
+                const SessionExpirationUpdateCB& session_expiration_update_cb,
+                bool privacy_mode);
+
+  void ResolvePromise(uint32_t promise_id);
+  void ChangeKeyStatuses(const std::string& ieme_session_id);
+  // Updates key statuses map based on given map. Notifies about new key and
+  // calls session_keys_change_cb
+  void UpdateKeyStatuses(const std::string& ieme_session_id,
+                         const eme::KeyStatusMap&);
+  // Checks session expiration time and if changed calls
+  // NotifySessionExpirationUpdate.
+  void UpdateExpirationTime(const std::string& ieme_session_id);
+  void RemoveComplete(const std::string& ieme_session_id);
+
+  void NotifySessionExpirationUpdate(const std::string& ieme_session_id,
+                                     int64_t expiry_time_ms);
+  void GenerateServerCertificateRequest(
+      std::unique_ptr<media::NewSessionCdmPromise> promise);
+  void DecryptInChromium(StreamType stream_type,
+                         const scoped_refptr<DecoderBuffer> encrypted,
+                         DecryptCBTizen decrypt_cb);
+#if BUILDFLAG(IS_TIZEN_TV) && TIZEN_VERSION_AT_LEAST(6, 0, 0)
+  bool IsKeyReady(const std::string& key);
+#endif
+
+  // DRM API requires us to call destroy instead of the destructor
+  struct IEMEDelete {
+    inline void operator()(eme::IEME* cdm) const { eme::IEME::destroy(cdm); }
+  };
+
+  std::unique_ptr<eme::IEME, IEMEDelete> cdm_;
+
+  // Key system name which was used to create |cdm_|
+  const std::string key_system_;
+
+  // Callbacks for firing session events.
+  SessionMessageCB session_message_cb_;
+  SessionClosedCB session_closed_cb_;
+  SessionKeysChangeCB session_keys_change_cb_;
+  SessionExpirationUpdateCB session_expiration_update_cb_;
+
+  // For resolving remove session promise
+  // IEME session id is used as a key
+  typedef std::unordered_map<std::string, uint32_t> SessionPromiseMap;
+  SessionPromiseMap session_promise_map_;
+  std::unique_ptr<CdmPromiseAdapter> cdm_promise_adapter_;
+  bool single_process_mode_;
+
+  // This map is for keeping track of keys and detecting whether new key has
+  // been added. IEME session id is used as a key
+  typedef std::unordered_map<std::string, CdmKeyInformation::KeyStatus>
+      KayStatusesMap;
+  std::unordered_map<std::string, KayStatusesMap> session_key_statuses_map_;
+
+  // Expiration data after last check
+  std::unordered_map<std::string, int64_t> last_expiration_times_map_;
+
+  // For DRM callbacks because they are not queuing their events on the event
+  // loop
+  const scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
+
+  CallbackRegistry<EventCB::RunType> event_callbacks_;
+
+  DecryptBufferFullStrategy decrypt_buffer_full_strategy_;
+
+  struct DecryptCBPair {
+    base::CancelableOnceClosure pending_decrypt_cb_;
+    IEMEDrmBridge::DecryptCBTizen last_decrypt_cb_;
+  };
+  std::map<StreamType, DecryptCBPair> decrypt_cb_map_;
+
+  base::WeakPtrFactory<IEMEDrmBridge> weak_factory_;
+};
+
+ContentDecryptionModule* CreateIEMEDrmBridge(
+    const std::string& key_system,
+    const SessionMessageCB& session_message_cb,
+    const SessionClosedCB& session_closed_cb,
+    const SessionKeysChangeCB& session_keys_change_cb,
+    const SessionExpirationUpdateCB& session_expiration_update_cb,
+    bool privacy_mode = false);
+}  // namespace media
+
+#endif  // MEDIA_BASE_ANDROID_IEME_DRM_BRIDGE_H_
index 839fcd8..f220826 100644 (file)
@@ -4,12 +4,22 @@
 
 #include "tizen_src/chromium_impl/media/filters/media_player_esplusplayer_tv.h"
 
+#include "base/command_line.h"
+#include "base/trace_event/trace_event.h"
+#include "content/public/common/content_switches.h"
 #include "media/base/decoder_buffer.h"
 #include "media/base/renderer_client.h"
+#include "tizen/system_info.h"
 #include "tizen_src/chromium_impl/media/filters/media_player_tizen_client.h"
 
+#include <drmdecrypt_api.h>
+
 namespace media {
 
+// Limit of platform player's total (audio and video) buffer size
+// for encrypted content in bytes
+const media::player_buffer_size_t kDRMPlayerTotalBufferSize = 10 * 1024 * 1024;
+
 MediaPlayerESPlusPlayerTV::MediaPlayerESPlusPlayerTV() {
   LOG(INFO) << "(" << static_cast<void*>(this) << ") " << __func__;
 }
@@ -20,6 +30,8 @@ MediaPlayerESPlusPlayerTV::~MediaPlayerESPlusPlayerTV() {
 
 void MediaPlayerESPlusPlayerTV::Initialize(VideoRendererSink* sink) {
   LOG(INFO) << "(" << static_cast<void*>(this) << ") " << __func__;
+  single_process_mode_ = base::CommandLine::ForCurrentProcess()->HasSwitch(
+      switches::kSingleProcess);
   MediaPlayerESPlusPlayer::Initialize(sink);
   if (GetMediaPlayerClient())
     GetMediaPlayerClient()->NotifyPlaybackState(kPlaybackLoad, player_id_);
@@ -58,10 +70,36 @@ void MediaPlayerESPlusPlayerTV::PostPrepareComplete() {
   MediaPlayerESPlusPlayer::PostPrepareComplete();
 }
 
+void MediaPlayerESPlusPlayerTV::SetAudioDecoderTypeIfNeeded(
+    const media::AudioCodec& codec) {
+  if (codec == media::AudioCodec::kFLAC) {
+    auto error = esplusplayer_set_audio_codec_type(
+        esplayer_, ESPLUSPLAYER_AUDIO_CODEC_TYPE_SW);
+    if (error != ESPLUSPLAYER_ERROR_TYPE_NONE) {
+      LOG(ERROR) << "esplusplayer_set_audio_codec_type failed, error code "
+                 << error;
+    }
+  }
+}
+
+void MediaPlayerESPlusPlayerTV::SetAudioSubmitDataType() {
+  if (video_codec_ == media::VideoCodec::kUnknown) {
+    // Emulator does not support DRM playback.
+    if (is_video_hole_ && !IsEmulatorArch())
+      SetSubmitDataType(true, single_process_mode_);
+    else
+      SetSubmitDataType(false, single_process_mode_);
+  }
+}
+
 int MediaPlayerESPlusPlayerTV::SetVideoStreamInfo(
     const media::VideoDecoderConfig& video_config,
     esplusplayer_video_stream_info& video_stream_info) {
   MediaPlayerESPlusPlayer::SetVideoStreamInfo(video_config, video_stream_info);
+  if (!SetVideoSubmitDataType(video_config)) {
+    LOG(ERROR) << "SetVideoSubmitDataType failed.";
+    return -1;
+  }
   return 0;
 }
 
@@ -69,12 +107,79 @@ void MediaPlayerESPlusPlayerTV::SetAudioStreamInfo(
     const media::AudioDecoderConfig& audio_config,
     esplusplayer_audio_stream_info* audio_stream_info) {
   MediaPlayerESPlusPlayer::SetAudioStreamInfo(audio_config, audio_stream_info);
+
+  SetAudioDecoderTypeIfNeeded(audio_config.codec());
+  SetAudioSubmitDataType();
 }
 
 bool MediaPlayerESPlusPlayerTV::ReadFromBufferQueue(DemuxerStream::Type type) {
   return MediaPlayerESPlusPlayer::ReadFromBufferQueue(type);
 }
 
+bool MediaPlayerESPlusPlayerTV::SetSubmitDataType(
+    const bool is_drm_eme,
+    const bool single_process_mode) {
+  int error = ESPLUSPLAYER_ERROR_TYPE_NONE;
+  is_drm_eme_ = is_drm_eme;
+  if (is_drm_eme) {
+    // only set submit data type for video,
+    // as this api has no stream type param.
+#if TIZEN_VERSION_AT_LEAST(6, 0, 0)
+    if (single_process_mode)
+      error = esplusplayer_set_submit_data_type(
+          esplayer_, ESPLUSPLAYER_SUBMIT_DATA_TYPE_ENCRYPTED_DATA);
+    else
+#endif
+      error = esplusplayer_set_submit_data_type(
+          esplayer_, ESPLUSPLAYER_SUBMIT_DATA_TYPE_TRUSTZONE_DATA);
+  } else {
+    error = esplusplayer_set_submit_data_type(
+        esplayer_, ESPLUSPLAYER_SUBMIT_DATA_TYPE_CLEAN_DATA);
+  }
+  if (error != ESPLUSPLAYER_ERROR_TYPE_NONE) {
+    LOG(ERROR) << "esplusplayer_set_submit_data_type failed. error code "
+               << error;
+    return false;
+  }
+  return true;
+}
+
+bool MediaPlayerESPlusPlayerTV::SetVideoSubmitDataType(
+    const media::VideoDecoderConfig& video_config) {
+  if (is_video_hole_ && !IsEmulatorArch() && !video_config.is_rtc()) {
+    is_video_drm_eme_ = true;
+  } else {
+    is_video_drm_eme_ = video_config.is_encrypted();
+  }
+
+  return SetSubmitDataType(is_video_drm_eme_, single_process_mode_);
+}
+
+player_buffer_size_t MediaPlayerESPlusPlayerTV::GetMaxVideoBufferSize(
+    const media::VideoDecoderConfig& video_config) {
+  const auto total_buffer_size = [this, &video_config]() -> uint64_t {
+    size_t used{};  // variable not used but required in call below
+    size_t total{};
+
+    if (!video_config.is_encrypted()) {
+      LOG(INFO) << "Clear content, using default";
+      return kPlayerTotalBufferSize;
+    }
+
+    if (eCDMReturnType::E_SUCCESS == ICDI::getDecryptStatus(&used, &total)) {
+      LOG(INFO) << "Decrypt status, total: " << (total / 1024) << "KB";
+      return total;
+    } else {
+      LOG(ERROR) << "Decrypt status failed, using default";
+      return kDRMPlayerTotalBufferSize;
+    }
+  }();
+  // There is not much sense setting MMPlayer's video buffer size larger
+  // that available TrustZone memory. Previously this caused out-of-memory
+  // being returned often during decryption.
+  return total_buffer_size - kPlayerAudioBufferSize;
+}
+
 bool MediaPlayerESPlusPlayerTV::SetBufferType() {
   esplusplayer_decoded_video_frame_buffer_type video_frame_buffer_type =
       is_video_hole_ ? ESPLUSPLAYER_DECODED_VIDEO_FRAME_BUFFER_TYPE_NONE
@@ -95,11 +200,79 @@ bool MediaPlayerESPlusPlayerTV::SetBufferType() {
 esplusplayer_submit_status MediaPlayerESPlusPlayerTV::SubmitEsPacket(
     DemuxerStream::Type type,
     scoped_refptr<DecoderBuffer> buffer) {
-  esplusplayer_submit_status status =
-      MediaPlayerESPlusPlayer::SubmitEsPacket(type, buffer);
+  TRACE_EVENT2("media", "MediaPlayerESPlusPlayerTV::SubmitEsPacket",
+               "key_frame", buffer->is_key_frame(), "timestamp",
+               buffer->timestamp().InMilliseconds());
+  esplusplayer_submit_status status = ESPLUSPLAYER_SUBMIT_STATUS_SUCCESS;
+  if (is_drm_eme_) {
+    esplusplayer_es_packet packet;
+    memset(&packet, 0, sizeof(esplusplayer_es_packet));
+    packet.type = GetESPlusPlayerStreamType(type);
+    if (buffer->tz_handle())
+      packet.buffer_size = buffer->tz_buffer_size();
+    else
+      packet.buffer_size = buffer->data_size();
+    packet.buffer = (char*)(const_cast<unsigned char*>(buffer->data()));
+    packet.pts = buffer->timestamp().InMilliseconds();
+    packet.duration = buffer->duration().InMilliseconds();
+
+    LOG(INFO) << __func__ << " " << DemuxerStream::GetTypeName(type) << " : "
+              << "Pushing esplus timestamp: " << packet.pts
+              << ", duration: " << packet.duration
+              << ", size: " << packet.buffer_size
+              << ", is_key_frame: " << buffer->is_key_frame()
+              << ", tz_handle: " << buffer->tz_handle();
+
+    // This filed only set when PushMediaPacket
+    packet.matroska_color_info = nullptr;
+
+    unsigned int tz_handle = buffer->tz_handle() ? buffer->tz_handle() : 0;
+#if TIZEN_VERSION_AT_LEAST(6, 0, 0)
+    if (single_process_mode_) {
+      if (buffer->decrypt_config() && buffer->decryptor_handle()) {
+#ifdef DRM_MAPI_AARCH_64
+        status = esplusplayer_submit_encrypted_packet_64bit(
+            esplayer_, &packet,
+            drm_info_.GetPlayerDrmInfo(buffer->decrypt_config(),
+                                       buffer->decryptor_handle()));
+#else
+        status = esplusplayer_submit_encrypted_packet(
+            esplayer_, &packet,
+            drm_info_.GetPlayerDrmInfo(buffer->decrypt_config(),
+                                       buffer->decryptor_handle()));
+#endif
+      } else
+        status =
+            esplusplayer_submit_encrypted_packet(esplayer_, &packet, nullptr);
+    } else
+#endif
+      status =
+          esplusplayer_submit_trust_zone_packet(esplayer_, &packet, tz_handle);
+    if (status == ESPLUSPLAYER_SUBMIT_STATUS_FULL ||
+        status == ESPLUSPLAYER_SUBMIT_STATUS_OUT_OF_MEMORY ||
+        status == ESPLUSPLAYER_SUBMIT_STATUS_NOT_PREPARED) {
+      LOG(WARNING) << DemuxerStream::GetTypeName(type)
+                   << " buffer full, won't feed";
+      SetShouldFeed(type, false);
+      return status;
+    } else if (status != ESPLUSPLAYER_SUBMIT_STATUS_SUCCESS) {
+      LOG(WARNING) << "submit " << DemuxerStream::GetTypeName(type)
+                   << " packet : " << GetString(status);
+      // media need release tz_handle when submit failed.
+      // espp will release tz_handle when submit success.
+      ReleaseTzHandle(buffer->tz_handle(), packet.buffer_size);
+    }
+
+    last_frames_[type].first = base::Milliseconds(packet.pts);
+    last_frames_[type].second = base::Milliseconds(packet.duration);
+  } else {
+    status = MediaPlayerESPlusPlayer::SubmitEsPacket(type, buffer);
+  }
+
   return status;
 }
 
+
 void MediaPlayerESPlusPlayerTV::EnableLowLatencyMode() {
   NOTIMPLEMENTED();
 }
@@ -138,4 +311,106 @@ void MediaPlayerESPlusPlayerTV::OnEos() {
     GetMediaPlayerClient()->NotifyPlaybackState(kPlaybackFinish, player_id_);
 }
 
+void ReleaseTzHandle(int tz_handle, int size) {
+  if (!tz_handle || size <= 0) {
+    return;
+  }
+  handle_and_size_s handle{tz_handle, size, TZ_ENABLED_YES};
+
+  auto ret = release_handle(&handle);
+  if (ret) {
+    LOG(ERROR) << "Releasing tz_handle " << tz_handle << " tz_size " << size
+               << " failed. Error " << ret;
+  }
+  return;
+}
+
+#if TIZEN_VERSION_AT_LEAST(6, 0, 0)
+esplusplayer_drmb_es_cipher_algorithm EncryptionSchemeToEsppCipherAlgorithm(
+    EncryptionScheme encryption_scheme) {
+  switch (encryption_scheme) {
+    case EncryptionScheme::kCenc:
+      return ESPLUSPLAYER_DRMB_ES_CIPHER_ALGORITHM_AES128_CTR;
+    case EncryptionScheme::kCbcs:
+      return ESPLUSPLAYER_DRMB_ES_CIPHER_ALGORITHM_AES128_CBC;
+    default:
+      LOG(WARNING) << "Unknown encryption scheme";
+      return ESPLUSPLAYER_DRMB_ES_CIPHER_ALGORITHM_AES128_CTR;
+  }
+}
+
+MediaPlayerESPlusPlayerTV::DrmInfo::DrmInfo() {
+#ifdef DRM_MAPI_AARCH_64
+  memset(&player_drm_info_, 0, sizeof(esplusplayer_drm_info_64bit));
+#else
+  memset(&player_drm_info_, 0, sizeof(esplusplayer_drm_info));
+#endif
+  memset(&subsample_info_, 0, sizeof(esplusplayer_drmb_es_subsample_info));
+  memset(&subsamples_data_, 0, sizeof(esplusplayer_drmb_es_fmp4_data));
+
+  player_drm_info_.algorithm = ESPLUSPLAYER_DRMB_ES_CIPHER_ALGORITHM_AES128_CTR;
+  player_drm_info_.format = ESPLUSPLAYER_DRMB_ES_MEDIA_FORMAT_FMP4;
+  player_drm_info_.phase = ESPLUSPLAYER_DRMB_ES_CIPHER_PHASE_NONE;
+  player_drm_info_.split_offsets = nullptr;
+  player_drm_info_.use_out_buffer = false;
+  player_drm_info_.use_pattern = false;
+  player_drm_info_.crypt_byte_block = 0;
+  player_drm_info_.skip_byte_block = 0;
+}
+
+#ifdef DRM_MAPI_AARCH_64
+esplusplayer_drm_info_64bit*
+MediaPlayerESPlusPlayerTV::DrmInfo::GetPlayerDrmInfo(
+    const DecryptConfig* config,
+    const size_t& decryptor) {
+#else
+esplusplayer_drm_info* MediaPlayerESPlusPlayerTV::DrmInfo::GetPlayerDrmInfo(
+    const DecryptConfig* config,
+    const size_t& decryptor) {
+#endif
+  if (!config || !decryptor) {
+    LOG(ERROR) << "config or decryptor is null";
+    return nullptr;
+  }
+  infos_.clear();
+  infos_.reserve(config->subsamples().size());
+  for (const auto& sub : config->subsamples()) {
+    subsample_info_.bytes_of_clear_data = sub.clear_bytes;
+    subsample_info_.bytes_of_encrypted_data = sub.cypher_bytes;
+    infos_.emplace_back(subsample_info_);
+  }
+  subsamples_data_.subsample_count = infos_.size();
+  subsamples_data_.subsample_infos = infos_.data();
+  player_drm_info_.algorithm =
+      EncryptionSchemeToEsppCipherAlgorithm(config->encryption_scheme());
+  player_drm_info_.handle = static_cast<eme::eme_decryptor_t>(decryptor);
+  player_drm_info_.kid = reinterpret_cast<unsigned char*>(
+      const_cast<char*>(config->key_id().data()));
+  player_drm_info_.kid_length = config->key_id().length();
+  player_drm_info_.iv =
+      reinterpret_cast<unsigned char*>(const_cast<char*>(config->iv().data()));
+  player_drm_info_.iv_length = config->iv().length();
+  player_drm_info_.sub_data = &subsamples_data_;
+  player_drm_info_.use_pattern = config->encryption_pattern().has_value();
+
+  if (config->encryption_pattern()) {
+    player_drm_info_.crypt_byte_block =
+        config->encryption_pattern()->crypt_byte_block();
+    player_drm_info_.skip_byte_block =
+        config->encryption_pattern()->skip_byte_block();
+  }
+
+  DLOG(INFO) << "enc algorithm: "
+             << static_cast<int>(config->encryption_scheme())
+             << ", subsamples_data_.subsample_count: "
+             << subsamples_data_.subsample_count
+             << ", subsamples_data_.subsample_infos: "
+             << subsamples_data_.subsample_infos
+             << ", use pattern: " << player_drm_info_.use_pattern
+             << ", crypt byte block: " << player_drm_info_.crypt_byte_block
+             << ", skip byte block: " << player_drm_info_.skip_byte_block;
+  return &player_drm_info_;
+}
+#endif
+
 }  // namespace media
index 37007fb..a063bbf 100644 (file)
@@ -5,12 +5,23 @@
 #ifndef MEDIA_FILTERS_MEDIA_PLAYER_ESPLUSPLAYER_TV_H_
 #define MEDIA_FILTERS_MEDIA_PLAYER_ESPLUSPLAYER_TV_H_
 
+#include "media/base/decrypt_config.h"
+#include "tizen_src/chromium_impl/build/tizen_version.h"
+
 #include "tizen_src/chromium_impl/media/filters/media_player_esplusplayer.h"
 
+#if TIZEN_VERSION_AT_LEAST(6, 0, 0)
+#include <drm.h>
+#include <drmdecrypt/emeCDM/IEME.h>
+#endif
+
 namespace media {
 
 class DemuxerStream;
 
+// Releases a handle from trusted zone.
+void ReleaseTzHandle(int tz_handle, int size);
+
 class MEDIA_EXPORT MediaPlayerESPlusPlayerTV : public MediaPlayerESPlusPlayer {
  public:
   MediaPlayerESPlusPlayerTV();
@@ -48,8 +59,45 @@ class MEDIA_EXPORT MediaPlayerESPlusPlayerTV : public MediaPlayerESPlusPlayer {
       scoped_refptr<DecoderBuffer> buffer) override;
   void InitializeStreamConfig(DemuxerStream::Type type) override;
   bool SetBufferType() override;
+  player_buffer_size_t GetMaxVideoBufferSize(
+      const media::VideoDecoderConfig& video_config) override;
 
  private:
+#if TIZEN_VERSION_AT_LEAST(6, 0, 0)
+  class DrmInfo {
+   public:
+    DrmInfo();
+#ifdef DRM_MAPI_AARCH_64
+    esplusplayer_drm_info_64bit* GetPlayerDrmInfo(const DecryptConfig* config,
+                                                  const size_t& decryptor);
+#else
+
+    esplusplayer_drm_info* GetPlayerDrmInfo(const DecryptConfig* config,
+                                            const size_t& decryptor);
+#endif
+
+   private:
+#ifdef DRM_MAPI_AARCH_64
+    esplusplayer_drm_info_64bit player_drm_info_;
+#else
+    esplusplayer_drm_info player_drm_info_;
+#endif
+    esplusplayer_drmb_es_subsample_info subsample_info_;
+    std::vector<esplusplayer_drmb_es_subsample_info> infos_;
+    esplusplayer_drmb_es_fmp4_data subsamples_data_;
+  };
+
+  DrmInfo drm_info_;
+#endif
+  bool SetVideoSubmitDataType(const media::VideoDecoderConfig& video_config);
+  void SetAudioSubmitDataType();
+  bool SetSubmitDataType(const bool is_drm_eme, const bool single_process_mode);
+  void SetAudioDecoderTypeIfNeeded(const media::AudioCodec& codec);
+
+  bool is_drm_eme_{false};
+  bool is_video_drm_eme_{false};
+  bool single_process_mode_{false};
+  media::VideoCodec video_codec_{media::VideoCodec::kUnknown};
   base::WeakPtrFactory<MediaPlayerESPlusPlayerTV> weak_factory_{this};
 };
 }  // namespace media
index edaf05c..ead5b06 100644 (file)
@@ -82,11 +82,16 @@ if (tizen_multimedia) {
 
   if (tizen_product_tv) {
     external_media_video_decode_sources += [
+      "//tizen_src/chromium_impl/media/filters/ieme_drm_bridge.cc",
+      "//tizen_src/chromium_impl/media/filters/ieme_drm_bridge.h",
       "//tizen_src/chromium_impl/media/filters/media_player_bridge_capi_tv.cc",
       "//tizen_src/chromium_impl/media/filters/media_player_bridge_capi_tv.h",
       "//tizen_src/chromium_impl/media/filters/media_player_esplusplayer_tv.cc",
       "//tizen_src/chromium_impl/media/filters/media_player_esplusplayer_tv.h",
     ]
+    external_media_video_decode_config += [
+      "//tizen_src/build:drmdecrypt",
+    ]
   } else {
     external_media_video_decode_sources += [
       "//tizen_src/chromium_impl/media/filters/media_player_esplusplayer_common.cc",
index e1ae1a6..49c252e 100644 (file)
@@ -65,7 +65,11 @@ typedef struct _Ewk_DRM_Init_Data_Info {
 typedef struct _Ewk_DRM_Init_Complete_Info {
   unsigned int pssh_data_length;
   unsigned char* pssh_data;
+#ifdef DRM_MAPI_AARCH_64
+  unsigned long decryptor;
+#else
   int decryptor;
+#endif
 } Ewk_DRM_Init_Complete_Info;
 
 typedef struct _Ewk_DRM_Error_Info {
index ccd86d7..e980ee7 100644 (file)
@@ -61,6 +61,7 @@
 #if BUILDFLAG(IS_TIZEN_TV)
 #include "common/application_type.h"
 #include "content/public/common/url_utils.h"
+#include "renderer/key_systems_tizen.h"
 #include "renderer/plugins/plugin_placeholder_avplayer.h"
 #endif
 
@@ -217,6 +218,17 @@ void ContentRendererClientEfl::WebViewCreated(
 #endif
 }
 
+void ContentRendererClientEfl::GetSupportedKeySystems(
+    media::GetSupportedKeySystemsCB cb) {
+#if BUILDFLAG(IS_TIZEN_TV)
+  // key_system changed by
+  // https://source.chromium.org/chromium/chromium/src/+/ecdc736223cfabe49a67f02b61f72b696a839588
+  media::KeySystemInfos key_systems;
+  efl_integration::TizenAddKeySystems({}, &key_systems);
+  std::move(cb).Run(std::move(key_systems));
+#endif
+}
+
 bool ContentRendererClientEfl::OverrideCreatePlugin(
     content::RenderFrame* render_frame,
     const blink::WebPluginParams& params,
index f3b57c3..0c795ab 100644 (file)
@@ -107,6 +107,8 @@ class ContentRendererClientEfl : public content::ContentRendererClient {
   bool shutting_down() const { return shutting_down_; }
   void set_shutting_down(bool shutting_down) { shutting_down_ = shutting_down; }
 
+  void GetSupportedKeySystems(media::GetSupportedKeySystemsCB cb) override;
+
  private:
   static void ApplyCustomMobileSettings(blink::WebView*);
 
@@ -118,6 +120,7 @@ class ContentRendererClientEfl : public content::ContentRendererClient {
 
 #if BUILDFLAG(IS_TIZEN_TV)
   bool floating_video_window_on_ = false;
+  std::vector<std::string> key_system_whitelist_;
 #endif
 };