OLD | NEW |
1 // Copyright 2013 The Chromium Authors. All rights reserved. | 1 // Copyright 2013 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "content/renderer/media/webmediaplayer_impl.h" | 5 #include "content/renderer/media/webmediaplayer_impl.h" |
6 | 6 |
7 #include <algorithm> | 7 #include <algorithm> |
8 #include <limits> | 8 #include <limits> |
9 #include <string> | 9 #include <string> |
10 #include <vector> | 10 #include <vector> |
(...skipping 261 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
272 if (url.SchemeIs("blob")) return kBlobURLScheme; | 272 if (url.SchemeIs("blob")) return kBlobURLScheme; |
273 if (url.SchemeIs("data")) return kDataURLScheme; | 273 if (url.SchemeIs("data")) return kDataURLScheme; |
274 if (url.SchemeIs("filesystem")) return kFileSystemScheme; | 274 if (url.SchemeIs("filesystem")) return kFileSystemScheme; |
275 return kUnknownURLScheme; | 275 return kUnknownURLScheme; |
276 } | 276 } |
277 | 277 |
278 } // namespace | 278 } // namespace |
279 | 279 |
280 void WebMediaPlayerImpl::load(LoadType load_type, const blink::WebURL& url, | 280 void WebMediaPlayerImpl::load(LoadType load_type, const blink::WebURL& url, |
281 CORSMode cors_mode) { | 281 CORSMode cors_mode) { |
| 282 DVLOG(1) << __FUNCTION__ << "(" << load_type << ", " << url << ", " |
| 283 << cors_mode << ")"; |
282 if (!defer_load_cb_.is_null()) { | 284 if (!defer_load_cb_.is_null()) { |
283 defer_load_cb_.Run(base::Bind( | 285 defer_load_cb_.Run(base::Bind( |
284 &WebMediaPlayerImpl::DoLoad, AsWeakPtr(), load_type, url, cors_mode)); | 286 &WebMediaPlayerImpl::DoLoad, AsWeakPtr(), load_type, url, cors_mode)); |
285 return; | 287 return; |
286 } | 288 } |
287 DoLoad(load_type, url, cors_mode); | 289 DoLoad(load_type, url, cors_mode); |
288 } | 290 } |
289 | 291 |
290 void WebMediaPlayerImpl::DoLoad(LoadType load_type, | 292 void WebMediaPlayerImpl::DoLoad(LoadType load_type, |
291 const blink::WebURL& url, | 293 const blink::WebURL& url, |
(...skipping 30 matching lines...) Expand all Loading... |
322 main_loop_, | 324 main_loop_, |
323 frame_, | 325 frame_, |
324 media_log_.get(), | 326 media_log_.get(), |
325 &buffered_data_source_host_, | 327 &buffered_data_source_host_, |
326 base::Bind(&WebMediaPlayerImpl::NotifyDownloading, AsWeakPtr()))); | 328 base::Bind(&WebMediaPlayerImpl::NotifyDownloading, AsWeakPtr()))); |
327 data_source_->Initialize( | 329 data_source_->Initialize( |
328 base::Bind(&WebMediaPlayerImpl::DataSourceInitialized, AsWeakPtr())); | 330 base::Bind(&WebMediaPlayerImpl::DataSourceInitialized, AsWeakPtr())); |
329 } | 331 } |
330 | 332 |
331 void WebMediaPlayerImpl::play() { | 333 void WebMediaPlayerImpl::play() { |
| 334 DVLOG(1) << __FUNCTION__; |
332 DCHECK(main_loop_->BelongsToCurrentThread()); | 335 DCHECK(main_loop_->BelongsToCurrentThread()); |
333 | 336 |
334 paused_ = false; | 337 paused_ = false; |
335 pipeline_.SetPlaybackRate(playback_rate_); | 338 pipeline_.SetPlaybackRate(playback_rate_); |
336 if (data_source_) | 339 if (data_source_) |
337 data_source_->MediaIsPlaying(); | 340 data_source_->MediaIsPlaying(); |
338 | 341 |
339 media_log_->AddEvent(media_log_->CreateEvent(media::MediaLogEvent::PLAY)); | 342 media_log_->AddEvent(media_log_->CreateEvent(media::MediaLogEvent::PLAY)); |
340 | 343 |
341 if (delegate_.get()) | 344 if (delegate_.get()) |
342 delegate_->DidPlay(this); | 345 delegate_->DidPlay(this); |
343 } | 346 } |
344 | 347 |
345 void WebMediaPlayerImpl::pause() { | 348 void WebMediaPlayerImpl::pause() { |
| 349 DVLOG(1) << __FUNCTION__; |
346 DCHECK(main_loop_->BelongsToCurrentThread()); | 350 DCHECK(main_loop_->BelongsToCurrentThread()); |
347 | 351 |
348 paused_ = true; | 352 paused_ = true; |
349 pipeline_.SetPlaybackRate(0.0f); | 353 pipeline_.SetPlaybackRate(0.0f); |
350 if (data_source_) | 354 if (data_source_) |
351 data_source_->MediaIsPaused(); | 355 data_source_->MediaIsPaused(); |
352 paused_time_ = pipeline_.GetMediaTime(); | 356 paused_time_ = pipeline_.GetMediaTime(); |
353 | 357 |
354 media_log_->AddEvent(media_log_->CreateEvent(media::MediaLogEvent::PAUSE)); | 358 media_log_->AddEvent(media_log_->CreateEvent(media::MediaLogEvent::PAUSE)); |
355 | 359 |
356 if (delegate_.get()) | 360 if (delegate_.get()) |
357 delegate_->DidPause(this); | 361 delegate_->DidPause(this); |
358 } | 362 } |
359 | 363 |
360 bool WebMediaPlayerImpl::supportsSave() const { | 364 bool WebMediaPlayerImpl::supportsSave() const { |
361 DCHECK(main_loop_->BelongsToCurrentThread()); | 365 DCHECK(main_loop_->BelongsToCurrentThread()); |
362 return supports_save_; | 366 return supports_save_; |
363 } | 367 } |
364 | 368 |
365 void WebMediaPlayerImpl::seek(double seconds) { | 369 void WebMediaPlayerImpl::seek(double seconds) { |
| 370 DVLOG(1) << __FUNCTION__ << "(" << seconds << ")"; |
366 DCHECK(main_loop_->BelongsToCurrentThread()); | 371 DCHECK(main_loop_->BelongsToCurrentThread()); |
367 | 372 |
368 if (ready_state_ > WebMediaPlayer::ReadyStateHaveMetadata) | 373 if (ready_state_ > WebMediaPlayer::ReadyStateHaveMetadata) |
369 SetReadyState(WebMediaPlayer::ReadyStateHaveMetadata); | 374 SetReadyState(WebMediaPlayer::ReadyStateHaveMetadata); |
370 | 375 |
371 base::TimeDelta seek_time = ConvertSecondsToTimestamp(seconds); | 376 base::TimeDelta seek_time = ConvertSecondsToTimestamp(seconds); |
372 | 377 |
373 if (starting_ || seeking_) { | 378 if (starting_ || seeking_) { |
374 pending_seek_ = true; | 379 pending_seek_ = true; |
375 pending_seek_seconds_ = seconds; | 380 pending_seek_seconds_ = seconds; |
(...skipping 13 matching lines...) Expand all Loading... |
389 if (chunk_demuxer_) | 394 if (chunk_demuxer_) |
390 chunk_demuxer_->StartWaitingForSeek(seek_time); | 395 chunk_demuxer_->StartWaitingForSeek(seek_time); |
391 | 396 |
392 // Kick off the asynchronous seek! | 397 // Kick off the asynchronous seek! |
393 pipeline_.Seek( | 398 pipeline_.Seek( |
394 seek_time, | 399 seek_time, |
395 BIND_TO_RENDER_LOOP(&WebMediaPlayerImpl::OnPipelineSeek)); | 400 BIND_TO_RENDER_LOOP(&WebMediaPlayerImpl::OnPipelineSeek)); |
396 } | 401 } |
397 | 402 |
398 void WebMediaPlayerImpl::setRate(double rate) { | 403 void WebMediaPlayerImpl::setRate(double rate) { |
| 404 DVLOG(1) << __FUNCTION__ << "(" << rate << ")"; |
399 DCHECK(main_loop_->BelongsToCurrentThread()); | 405 DCHECK(main_loop_->BelongsToCurrentThread()); |
400 | 406 |
401 // TODO(kylep): Remove when support for negatives is added. Also, modify the | 407 // TODO(kylep): Remove when support for negatives is added. Also, modify the |
402 // following checks so rewind uses reasonable values also. | 408 // following checks so rewind uses reasonable values also. |
403 if (rate < 0.0) | 409 if (rate < 0.0) |
404 return; | 410 return; |
405 | 411 |
406 // Limit rates to reasonable values by clamping. | 412 // Limit rates to reasonable values by clamping. |
407 if (rate != 0.0) { | 413 if (rate != 0.0) { |
408 if (rate < kMinRate) | 414 if (rate < kMinRate) |
409 rate = kMinRate; | 415 rate = kMinRate; |
410 else if (rate > kMaxRate) | 416 else if (rate > kMaxRate) |
411 rate = kMaxRate; | 417 rate = kMaxRate; |
412 } | 418 } |
413 | 419 |
414 playback_rate_ = rate; | 420 playback_rate_ = rate; |
415 if (!paused_) { | 421 if (!paused_) { |
416 pipeline_.SetPlaybackRate(rate); | 422 pipeline_.SetPlaybackRate(rate); |
417 if (data_source_) | 423 if (data_source_) |
418 data_source_->MediaPlaybackRateChanged(rate); | 424 data_source_->MediaPlaybackRateChanged(rate); |
419 } | 425 } |
420 } | 426 } |
421 | 427 |
422 void WebMediaPlayerImpl::setVolume(double volume) { | 428 void WebMediaPlayerImpl::setVolume(double volume) { |
| 429 DVLOG(1) << __FUNCTION__ << "(" << volume << ")"; |
423 DCHECK(main_loop_->BelongsToCurrentThread()); | 430 DCHECK(main_loop_->BelongsToCurrentThread()); |
424 | 431 |
425 pipeline_.SetVolume(volume); | 432 pipeline_.SetVolume(volume); |
426 } | 433 } |
427 | 434 |
428 #define COMPILE_ASSERT_MATCHING_ENUM(webkit_name, chromium_name) \ | 435 #define COMPILE_ASSERT_MATCHING_ENUM(webkit_name, chromium_name) \ |
429 COMPILE_ASSERT(static_cast<int>(WebMediaPlayer::webkit_name) == \ | 436 COMPILE_ASSERT(static_cast<int>(WebMediaPlayer::webkit_name) == \ |
430 static_cast<int>(content::chromium_name), \ | 437 static_cast<int>(content::chromium_name), \ |
431 mismatching_enums) | 438 mismatching_enums) |
432 COMPILE_ASSERT_MATCHING_ENUM(PreloadNone, NONE); | 439 COMPILE_ASSERT_MATCHING_ENUM(PreloadNone, NONE); |
433 COMPILE_ASSERT_MATCHING_ENUM(PreloadMetaData, METADATA); | 440 COMPILE_ASSERT_MATCHING_ENUM(PreloadMetaData, METADATA); |
434 COMPILE_ASSERT_MATCHING_ENUM(PreloadAuto, AUTO); | 441 COMPILE_ASSERT_MATCHING_ENUM(PreloadAuto, AUTO); |
435 #undef COMPILE_ASSERT_MATCHING_ENUM | 442 #undef COMPILE_ASSERT_MATCHING_ENUM |
436 | 443 |
437 void WebMediaPlayerImpl::setPreload(WebMediaPlayer::Preload preload) { | 444 void WebMediaPlayerImpl::setPreload(WebMediaPlayer::Preload preload) { |
| 445 DVLOG(1) << __FUNCTION__ << "(" << preload << ")"; |
438 DCHECK(main_loop_->BelongsToCurrentThread()); | 446 DCHECK(main_loop_->BelongsToCurrentThread()); |
439 | 447 |
440 if (data_source_) | 448 if (data_source_) |
441 data_source_->SetPreload(static_cast<content::Preload>(preload)); | 449 data_source_->SetPreload(static_cast<content::Preload>(preload)); |
442 } | 450 } |
443 | 451 |
444 bool WebMediaPlayerImpl::hasVideo() const { | 452 bool WebMediaPlayerImpl::hasVideo() const { |
445 DCHECK(main_loop_->BelongsToCurrentThread()); | 453 DCHECK(main_loop_->BelongsToCurrentThread()); |
446 | 454 |
447 return pipeline_metadata_.has_video; | 455 return pipeline_metadata_.has_video; |
(...skipping 469 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
917 } | 925 } |
918 | 926 |
919 void WebMediaPlayerImpl::InvalidateOnMainThread() { | 927 void WebMediaPlayerImpl::InvalidateOnMainThread() { |
920 DCHECK(main_loop_->BelongsToCurrentThread()); | 928 DCHECK(main_loop_->BelongsToCurrentThread()); |
921 TRACE_EVENT0("media", "WebMediaPlayerImpl::InvalidateOnMainThread"); | 929 TRACE_EVENT0("media", "WebMediaPlayerImpl::InvalidateOnMainThread"); |
922 | 930 |
923 client_->repaint(); | 931 client_->repaint(); |
924 } | 932 } |
925 | 933 |
926 void WebMediaPlayerImpl::OnPipelineSeek(PipelineStatus status) { | 934 void WebMediaPlayerImpl::OnPipelineSeek(PipelineStatus status) { |
| 935 DVLOG(1) << __FUNCTION__ << "(" << status << ")"; |
927 DCHECK(main_loop_->BelongsToCurrentThread()); | 936 DCHECK(main_loop_->BelongsToCurrentThread()); |
928 starting_ = false; | 937 starting_ = false; |
929 seeking_ = false; | 938 seeking_ = false; |
930 if (pending_seek_) { | 939 if (pending_seek_) { |
931 pending_seek_ = false; | 940 pending_seek_ = false; |
932 seek(pending_seek_seconds_); | 941 seek(pending_seek_seconds_); |
933 return; | 942 return; |
934 } | 943 } |
935 | 944 |
936 if (status != media::PIPELINE_OK) { | 945 if (status != media::PIPELINE_OK) { |
937 OnPipelineError(status); | 946 OnPipelineError(status); |
938 return; | 947 return; |
939 } | 948 } |
940 | 949 |
941 // Update our paused time. | 950 // Update our paused time. |
942 if (paused_) | 951 if (paused_) |
943 paused_time_ = pipeline_.GetMediaTime(); | 952 paused_time_ = pipeline_.GetMediaTime(); |
944 | 953 |
945 client_->timeChanged(); | 954 client_->timeChanged(); |
946 } | 955 } |
947 | 956 |
948 void WebMediaPlayerImpl::OnPipelineEnded() { | 957 void WebMediaPlayerImpl::OnPipelineEnded() { |
| 958 DVLOG(1) << __FUNCTION__; |
949 DCHECK(main_loop_->BelongsToCurrentThread()); | 959 DCHECK(main_loop_->BelongsToCurrentThread()); |
950 client_->timeChanged(); | 960 client_->timeChanged(); |
951 } | 961 } |
952 | 962 |
953 void WebMediaPlayerImpl::OnPipelineError(PipelineStatus error) { | 963 void WebMediaPlayerImpl::OnPipelineError(PipelineStatus error) { |
954 DCHECK(main_loop_->BelongsToCurrentThread()); | 964 DCHECK(main_loop_->BelongsToCurrentThread()); |
955 DCHECK_NE(error, media::PIPELINE_OK); | 965 DCHECK_NE(error, media::PIPELINE_OK); |
956 | 966 |
957 if (ready_state_ == WebMediaPlayer::ReadyStateHaveNothing) { | 967 if (ready_state_ == WebMediaPlayer::ReadyStateHaveNothing) { |
958 // Any error that occurs before reaching ReadyStateHaveMetadata should | 968 // Any error that occurs before reaching ReadyStateHaveMetadata should |
(...skipping 11 matching lines...) Expand all Loading... |
970 if (error == media::PIPELINE_ERROR_DECRYPT) | 980 if (error == media::PIPELINE_ERROR_DECRYPT) |
971 EmeUMAHistogramCounts(current_key_system_, "DecryptError", 1); | 981 EmeUMAHistogramCounts(current_key_system_, "DecryptError", 1); |
972 | 982 |
973 // TODO(scherkus): This should be handled by HTMLMediaElement and controls | 983 // TODO(scherkus): This should be handled by HTMLMediaElement and controls |
974 // should know when to invalidate themselves http://crbug.com/337015 | 984 // should know when to invalidate themselves http://crbug.com/337015 |
975 InvalidateOnMainThread(); | 985 InvalidateOnMainThread(); |
976 } | 986 } |
977 | 987 |
978 void WebMediaPlayerImpl::OnPipelineMetadata( | 988 void WebMediaPlayerImpl::OnPipelineMetadata( |
979 media::PipelineMetadata metadata) { | 989 media::PipelineMetadata metadata) { |
980 DVLOG(1) << "OnPipelineMetadata"; | 990 DVLOG(1) << __FUNCTION__; |
981 | 991 |
982 pipeline_metadata_ = metadata; | 992 pipeline_metadata_ = metadata; |
983 | 993 |
984 SetReadyState(WebMediaPlayer::ReadyStateHaveMetadata); | 994 SetReadyState(WebMediaPlayer::ReadyStateHaveMetadata); |
985 | 995 |
986 if (hasVideo()) { | 996 if (hasVideo()) { |
987 DCHECK(!video_weblayer_); | 997 DCHECK(!video_weblayer_); |
988 video_weblayer_.reset( | 998 video_weblayer_.reset( |
989 new webkit::WebLayerImpl(cc::VideoLayer::Create(compositor_))); | 999 new webkit::WebLayerImpl(cc::VideoLayer::Create(compositor_))); |
990 video_weblayer_->setOpaque(opaque_); | 1000 video_weblayer_->setOpaque(opaque_); |
991 client_->setWebLayer(video_weblayer_.get()); | 1001 client_->setWebLayer(video_weblayer_.get()); |
992 } | 1002 } |
993 | 1003 |
994 // TODO(scherkus): This should be handled by HTMLMediaElement and controls | 1004 // TODO(scherkus): This should be handled by HTMLMediaElement and controls |
995 // should know when to invalidate themselves http://crbug.com/337015 | 1005 // should know when to invalidate themselves http://crbug.com/337015 |
996 InvalidateOnMainThread(); | 1006 InvalidateOnMainThread(); |
997 } | 1007 } |
998 | 1008 |
999 void WebMediaPlayerImpl::OnPipelinePrerollCompleted() { | 1009 void WebMediaPlayerImpl::OnPipelinePrerollCompleted() { |
1000 DVLOG(1) << "OnPipelinePrerollCompleted"; | 1010 DVLOG(1) << __FUNCTION__; |
1001 | 1011 |
1002 // Only transition to ReadyStateHaveEnoughData if we don't have | 1012 // Only transition to ReadyStateHaveEnoughData if we don't have |
1003 // any pending seeks because the transition can cause Blink to | 1013 // any pending seeks because the transition can cause Blink to |
1004 // report that the most recent seek has completed. | 1014 // report that the most recent seek has completed. |
1005 if (!pending_seek_) { | 1015 if (!pending_seek_) { |
1006 SetReadyState(WebMediaPlayer::ReadyStateHaveEnoughData); | 1016 SetReadyState(WebMediaPlayer::ReadyStateHaveEnoughData); |
1007 | 1017 |
1008 // TODO(scherkus): This should be handled by HTMLMediaElement and controls | 1018 // TODO(scherkus): This should be handled by HTMLMediaElement and controls |
1009 // should know when to invalidate themselves http://crbug.com/337015 | 1019 // should know when to invalidate themselves http://crbug.com/337015 |
1010 InvalidateOnMainThread(); | 1020 InvalidateOnMainThread(); |
(...skipping 215 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1226 filter_collection.Pass(), | 1236 filter_collection.Pass(), |
1227 BIND_TO_RENDER_LOOP(&WebMediaPlayerImpl::OnPipelineEnded), | 1237 BIND_TO_RENDER_LOOP(&WebMediaPlayerImpl::OnPipelineEnded), |
1228 BIND_TO_RENDER_LOOP(&WebMediaPlayerImpl::OnPipelineError), | 1238 BIND_TO_RENDER_LOOP(&WebMediaPlayerImpl::OnPipelineError), |
1229 BIND_TO_RENDER_LOOP(&WebMediaPlayerImpl::OnPipelineSeek), | 1239 BIND_TO_RENDER_LOOP(&WebMediaPlayerImpl::OnPipelineSeek), |
1230 BIND_TO_RENDER_LOOP(&WebMediaPlayerImpl::OnPipelineMetadata), | 1240 BIND_TO_RENDER_LOOP(&WebMediaPlayerImpl::OnPipelineMetadata), |
1231 BIND_TO_RENDER_LOOP(&WebMediaPlayerImpl::OnPipelinePrerollCompleted), | 1241 BIND_TO_RENDER_LOOP(&WebMediaPlayerImpl::OnPipelinePrerollCompleted), |
1232 BIND_TO_RENDER_LOOP(&WebMediaPlayerImpl::OnDurationChanged)); | 1242 BIND_TO_RENDER_LOOP(&WebMediaPlayerImpl::OnDurationChanged)); |
1233 } | 1243 } |
1234 | 1244 |
1235 void WebMediaPlayerImpl::SetNetworkState(WebMediaPlayer::NetworkState state) { | 1245 void WebMediaPlayerImpl::SetNetworkState(WebMediaPlayer::NetworkState state) { |
| 1246 DVLOG(1) << __FUNCTION__ << "(" << state << ")"; |
1236 DCHECK(main_loop_->BelongsToCurrentThread()); | 1247 DCHECK(main_loop_->BelongsToCurrentThread()); |
1237 DVLOG(1) << "SetNetworkState: " << state; | |
1238 network_state_ = state; | 1248 network_state_ = state; |
1239 // Always notify to ensure client has the latest value. | 1249 // Always notify to ensure client has the latest value. |
1240 client_->networkStateChanged(); | 1250 client_->networkStateChanged(); |
1241 } | 1251 } |
1242 | 1252 |
1243 void WebMediaPlayerImpl::SetReadyState(WebMediaPlayer::ReadyState state) { | 1253 void WebMediaPlayerImpl::SetReadyState(WebMediaPlayer::ReadyState state) { |
| 1254 DVLOG(1) << __FUNCTION__ << "(" << state << ")"; |
1244 DCHECK(main_loop_->BelongsToCurrentThread()); | 1255 DCHECK(main_loop_->BelongsToCurrentThread()); |
1245 DVLOG(1) << "SetReadyState: " << state; | |
1246 | 1256 |
1247 if (state == WebMediaPlayer::ReadyStateHaveEnoughData && data_source_ && | 1257 if (state == WebMediaPlayer::ReadyStateHaveEnoughData && data_source_ && |
1248 data_source_->assume_fully_buffered() && | 1258 data_source_->assume_fully_buffered() && |
1249 network_state_ == WebMediaPlayer::NetworkStateLoading) | 1259 network_state_ == WebMediaPlayer::NetworkStateLoading) |
1250 SetNetworkState(WebMediaPlayer::NetworkStateLoaded); | 1260 SetNetworkState(WebMediaPlayer::NetworkStateLoaded); |
1251 | 1261 |
1252 ready_state_ = state; | 1262 ready_state_ = state; |
1253 // Always notify to ensure client has the latest value. | 1263 // Always notify to ensure client has the latest value. |
1254 client_->readyStateChanged(); | 1264 client_->readyStateChanged(); |
1255 } | 1265 } |
(...skipping 113 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1369 compositor_task_runner_->PostTask(FROM_HERE, | 1379 compositor_task_runner_->PostTask(FROM_HERE, |
1370 base::Bind(&GetCurrentFrameAndSignal, | 1380 base::Bind(&GetCurrentFrameAndSignal, |
1371 base::Unretained(compositor_), | 1381 base::Unretained(compositor_), |
1372 &video_frame, | 1382 &video_frame, |
1373 &event)); | 1383 &event)); |
1374 event.Wait(); | 1384 event.Wait(); |
1375 return video_frame; | 1385 return video_frame; |
1376 } | 1386 } |
1377 | 1387 |
1378 } // namespace content | 1388 } // namespace content |
OLD | NEW |