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>
#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
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());
#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;
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
#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.
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);
// 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 {
// 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>;
// 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;
return "need_more_data";
case kError:
return "error";
+#if BUILDFLAG(IS_TIZEN_TV)
+ case kBufferFull:
+ return "buffer_full";
+#endif
}
}
#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;
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 };
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
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;
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))
#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;
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();
#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.
}
#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__;
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;
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_);
// 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() {
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()) {
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(
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;
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;
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());
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>>());
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;
};
%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}
%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}" \
if (build_chrome) {
defines += [ "BUILD_CHROME" ]
}
+
+ if (drm_mapi_aarch_64) {
+ defines += [ "DRM_MAPI_AARCH_64" ]
+ }
}
tizen_resource_manager = false
enable_network_camera = false
tizen_thread_booster_service = false
+
+ drm_mapi_aarch_64 = false
}
if (is_tizen && !build_chrome) {
--- /dev/null
+// 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
--- /dev/null
+// 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
"//tizen_src/build:libtts",
]
+if (tizen_multimedia) {
+ external_content_renderer_efl_configs += [
+ "//tizen_src/build:drmdecrypt",
+ ]
+}
+
##############################################################################
# Dependency
##############################################################################
"//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",
+ ]
+}
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;
--- /dev/null
+// 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 = ¶ms;
+
+ // 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(), ¶ms_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
--- /dev/null
+// 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_
#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__;
}
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_);
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;
}
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
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();
}
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
#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();
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
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",
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 {
#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
#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,
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*);
#if BUILDFLAG(IS_TIZEN_TV)
bool floating_video_window_on_ = false;
+ std::vector<std::string> key_system_whitelist_;
#endif
};