OLD | NEW |
1 /* | 1 /* |
2 * Copyright (C) 2007, 2008, 2009, 2010, 2011, 2012, 2013 Apple Inc. All rights
reserved. | 2 * Copyright (C) 2007, 2008, 2009, 2010, 2011, 2012, 2013 Apple Inc. All rights
reserved. |
3 * | 3 * |
4 * Redistribution and use in source and binary forms, with or without | 4 * Redistribution and use in source and binary forms, with or without |
5 * modification, are permitted provided that the following conditions | 5 * modification, are permitted provided that the following conditions |
6 * are met: | 6 * are met: |
7 * 1. Redistributions of source code must retain the above copyright | 7 * 1. Redistributions of source code must retain the above copyright |
8 * notice, this list of conditions and the following disclaimer. | 8 * notice, this list of conditions and the following disclaimer. |
9 * 2. Redistributions in binary form must reproduce the above copyright | 9 * 2. Redistributions in binary form must reproduce the above copyright |
10 * notice, this list of conditions and the following disclaimer in the | 10 * notice, this list of conditions and the following disclaimer in the |
(...skipping 609 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
620 configureMediaControls(); | 620 configureMediaControls(); |
621 if (m_networkState > NETWORK_EMPTY) | 621 if (m_networkState > NETWORK_EMPTY) |
622 pauseInternal(); | 622 pauseInternal(); |
623 } | 623 } |
624 } | 624 } |
625 | 625 |
626 void HTMLMediaElement::attach(const AttachContext& context) | 626 void HTMLMediaElement::attach(const AttachContext& context) |
627 { | 627 { |
628 HTMLElement::attach(context); | 628 HTMLElement::attach(context); |
629 | 629 |
630 if (layoutObject()) | 630 if (hasLayoutObject()) |
631 layoutObject()->updateFromElement(); | 631 layoutObject()->updateFromElement(); |
632 } | 632 } |
633 | 633 |
634 void HTMLMediaElement::didRecalcStyle(StyleRecalcChange) | 634 void HTMLMediaElement::didRecalcStyle(StyleRecalcChange) |
635 { | 635 { |
636 if (layoutObject()) | 636 if (hasLayoutObject()) |
637 layoutObject()->updateFromElement(); | 637 layoutObject()->updateFromElement(); |
638 } | 638 } |
639 | 639 |
640 void HTMLMediaElement::scheduleTextTrackResourceLoad() | 640 void HTMLMediaElement::scheduleTextTrackResourceLoad() |
641 { | 641 { |
642 MEDIA_LOG << "scheduleTextTrackResourceLoad(" << (void*)this << ")"; | 642 MEDIA_LOG << "scheduleTextTrackResourceLoad(" << (void*)this << ")"; |
643 | 643 |
644 m_pendingActionFlags |= LoadTextTrackResource; | 644 m_pendingActionFlags |= LoadTextTrackResource; |
645 | 645 |
646 if (!m_loadTimer.isActive()) | 646 if (!m_loadTimer.isActive()) |
(...skipping 403 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1050 startPlayerLoad(); | 1050 startPlayerLoad(); |
1051 } | 1051 } |
1052 } else { | 1052 } else { |
1053 mediaLoadingFailed(WebMediaPlayer::NetworkStateFormatError); | 1053 mediaLoadingFailed(WebMediaPlayer::NetworkStateFormatError); |
1054 } | 1054 } |
1055 | 1055 |
1056 // If there is no poster to display, allow the media engine to render video
frames as soon as | 1056 // If there is no poster to display, allow the media engine to render video
frames as soon as |
1057 // they are available. | 1057 // they are available. |
1058 updateDisplayState(); | 1058 updateDisplayState(); |
1059 | 1059 |
1060 if (layoutObject()) | 1060 if (hasLayoutObject()) |
1061 layoutObject()->updateFromElement(); | 1061 layoutObject()->updateFromElement(); |
1062 } | 1062 } |
1063 | 1063 |
1064 void HTMLMediaElement::startPlayerLoad(const KURL& playerProvidedUrl) | 1064 void HTMLMediaElement::startPlayerLoad(const KURL& playerProvidedUrl) |
1065 { | 1065 { |
1066 DCHECK(!m_webMediaPlayer); | 1066 DCHECK(!m_webMediaPlayer); |
1067 | 1067 |
1068 WebMediaPlayerSource source; | 1068 WebMediaPlayerSource source; |
1069 if (m_srcObject) { | 1069 if (m_srcObject) { |
1070 source = WebMediaPlayerSource(WebMediaStream(m_srcObject)); | 1070 source = WebMediaPlayerSource(WebMediaStream(m_srcObject)); |
(...skipping 28 matching lines...) Expand all Loading... |
1099 mediaLoadingFailed(WebMediaPlayer::NetworkStateFormatError); | 1099 mediaLoadingFailed(WebMediaPlayer::NetworkStateFormatError); |
1100 return; | 1100 return; |
1101 } | 1101 } |
1102 | 1102 |
1103 m_webMediaPlayer = frame->loader().client()->createWebMediaPlayer(*this, sou
rce, this); | 1103 m_webMediaPlayer = frame->loader().client()->createWebMediaPlayer(*this, sou
rce, this); |
1104 if (!m_webMediaPlayer) { | 1104 if (!m_webMediaPlayer) { |
1105 mediaLoadingFailed(WebMediaPlayer::NetworkStateFormatError); | 1105 mediaLoadingFailed(WebMediaPlayer::NetworkStateFormatError); |
1106 return; | 1106 return; |
1107 } | 1107 } |
1108 | 1108 |
1109 if (layoutObject()) | 1109 if (hasLayoutObject()) |
1110 layoutObject()->setShouldDoFullPaintInvalidation(); | 1110 layoutObject()->setShouldDoFullPaintInvalidation(); |
1111 // Make sure if we create/re-create the WebMediaPlayer that we update our wr
apper. | 1111 // Make sure if we create/re-create the WebMediaPlayer that we update our wr
apper. |
1112 m_audioSourceProvider.wrap(m_webMediaPlayer->getAudioSourceProvider()); | 1112 m_audioSourceProvider.wrap(m_webMediaPlayer->getAudioSourceProvider()); |
1113 m_webMediaPlayer->setVolume(effectiveMediaVolume()); | 1113 m_webMediaPlayer->setVolume(effectiveMediaVolume()); |
1114 | 1114 |
1115 m_webMediaPlayer->setPoster(posterImageURL()); | 1115 m_webMediaPlayer->setPoster(posterImageURL()); |
1116 | 1116 |
1117 m_webMediaPlayer->setPreload(effectivePreloadType()); | 1117 m_webMediaPlayer->setPreload(effectivePreloadType()); |
1118 | 1118 |
1119 m_webMediaPlayer->load(loadType(), source, corsMode()); | 1119 m_webMediaPlayer->load(loadType(), source, corsMode()); |
(...skipping 195 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1315 m_loadState = WaitingForSource; | 1315 m_loadState = WaitingForSource; |
1316 | 1316 |
1317 // 6.17 - Waiting: Set the element's networkState attribute to the NETWORK_N
O_SOURCE value | 1317 // 6.17 - Waiting: Set the element's networkState attribute to the NETWORK_N
O_SOURCE value |
1318 setNetworkState(NETWORK_NO_SOURCE); | 1318 setNetworkState(NETWORK_NO_SOURCE); |
1319 | 1319 |
1320 // 6.18 - Set the element's delaying-the-load-event flag to false. This stop
s delaying the load event. | 1320 // 6.18 - Set the element's delaying-the-load-event flag to false. This stop
s delaying the load event. |
1321 setShouldDelayLoadEvent(false); | 1321 setShouldDelayLoadEvent(false); |
1322 | 1322 |
1323 updateDisplayState(); | 1323 updateDisplayState(); |
1324 | 1324 |
1325 if (layoutObject()) | 1325 if (hasLayoutObject()) |
1326 layoutObject()->updateFromElement(); | 1326 layoutObject()->updateFromElement(); |
1327 } | 1327 } |
1328 | 1328 |
1329 void HTMLMediaElement::noneSupported() | 1329 void HTMLMediaElement::noneSupported() |
1330 { | 1330 { |
1331 MEDIA_LOG << "noneSupported(" << (void*)this << ")"; | 1331 MEDIA_LOG << "noneSupported(" << (void*)this << ")"; |
1332 | 1332 |
1333 stopPeriodicTimers(); | 1333 stopPeriodicTimers(); |
1334 m_loadState = WaitingForSource; | 1334 m_loadState = WaitingForSource; |
1335 m_currentSourceNode = nullptr; | 1335 m_currentSourceNode = nullptr; |
(...skipping 18 matching lines...) Expand all Loading... |
1354 scheduleEvent(EventTypeNames::error); | 1354 scheduleEvent(EventTypeNames::error); |
1355 | 1355 |
1356 // 6 - Reject pending play promises with NotSupportedError. | 1356 // 6 - Reject pending play promises with NotSupportedError. |
1357 scheduleRejectPlayPromises(NotSupportedError); | 1357 scheduleRejectPlayPromises(NotSupportedError); |
1358 | 1358 |
1359 closeMediaSource(); | 1359 closeMediaSource(); |
1360 | 1360 |
1361 // 7 - Set the element's delaying-the-load-event flag to false. This stops d
elaying the load event. | 1361 // 7 - Set the element's delaying-the-load-event flag to false. This stops d
elaying the load event. |
1362 setShouldDelayLoadEvent(false); | 1362 setShouldDelayLoadEvent(false); |
1363 | 1363 |
1364 if (layoutObject()) | 1364 if (hasLayoutObject()) |
1365 layoutObject()->updateFromElement(); | 1365 layoutObject()->updateFromElement(); |
1366 } | 1366 } |
1367 | 1367 |
1368 void HTMLMediaElement::mediaEngineError(MediaError* err) | 1368 void HTMLMediaElement::mediaEngineError(MediaError* err) |
1369 { | 1369 { |
1370 DCHECK_GE(m_readyState, HAVE_METADATA); | 1370 DCHECK_GE(m_readyState, HAVE_METADATA); |
1371 MEDIA_LOG << "mediaEngineError(" << (void*)this << ", " << static_cast<int>(
err->code()) << ")"; | 1371 MEDIA_LOG << "mediaEngineError(" << (void*)this << ", " << static_cast<int>(
err->code()) << ")"; |
1372 | 1372 |
1373 // 1 - The user agent should cancel the fetching process. | 1373 // 1 - The user agent should cancel the fetching process. |
1374 stopPeriodicTimers(); | 1374 stopPeriodicTimers(); |
(...skipping 214 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1589 initialPlaybackPosition = 0; | 1589 initialPlaybackPosition = 0; |
1590 | 1590 |
1591 if (!jumped && initialPlaybackPosition > 0) { | 1591 if (!jumped && initialPlaybackPosition > 0) { |
1592 UseCounter::count(document(), UseCounter::HTMLMediaElementSeekToFrag
mentStart); | 1592 UseCounter::count(document(), UseCounter::HTMLMediaElementSeekToFrag
mentStart); |
1593 seek(initialPlaybackPosition); | 1593 seek(initialPlaybackPosition); |
1594 jumped = true; | 1594 jumped = true; |
1595 } | 1595 } |
1596 | 1596 |
1597 if (mediaControls()) | 1597 if (mediaControls()) |
1598 mediaControls()->reset(); | 1598 mediaControls()->reset(); |
1599 if (layoutObject()) | 1599 if (hasLayoutObject()) |
1600 layoutObject()->updateFromElement(); | 1600 layoutObject()->updateFromElement(); |
1601 } | 1601 } |
1602 | 1602 |
1603 bool shouldUpdateDisplayState = false; | 1603 bool shouldUpdateDisplayState = false; |
1604 | 1604 |
1605 if (m_readyState >= HAVE_CURRENT_DATA && oldState < HAVE_CURRENT_DATA && !m_
haveFiredLoadedData) { | 1605 if (m_readyState >= HAVE_CURRENT_DATA && oldState < HAVE_CURRENT_DATA && !m_
haveFiredLoadedData) { |
1606 m_haveFiredLoadedData = true; | 1606 m_haveFiredLoadedData = true; |
1607 shouldUpdateDisplayState = true; | 1607 shouldUpdateDisplayState = true; |
1608 scheduleEvent(EventTypeNames::loadeddata); | 1608 scheduleEvent(EventTypeNames::loadeddata); |
1609 setShouldDelayLoadEvent(false); | 1609 setShouldDelayLoadEvent(false); |
(...skipping 56 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1666 if (m_networkState != NETWORK_LOADING) | 1666 if (m_networkState != NETWORK_LOADING) |
1667 return; | 1667 return; |
1668 | 1668 |
1669 double time = WTF::currentTime(); | 1669 double time = WTF::currentTime(); |
1670 double timedelta = time - m_previousProgressTime; | 1670 double timedelta = time - m_previousProgressTime; |
1671 | 1671 |
1672 if (webMediaPlayer() && webMediaPlayer()->didLoadingProgress()) { | 1672 if (webMediaPlayer() && webMediaPlayer()->didLoadingProgress()) { |
1673 scheduleEvent(EventTypeNames::progress); | 1673 scheduleEvent(EventTypeNames::progress); |
1674 m_previousProgressTime = time; | 1674 m_previousProgressTime = time; |
1675 m_sentStalledEvent = false; | 1675 m_sentStalledEvent = false; |
1676 if (layoutObject()) | 1676 if (hasLayoutObject()) |
1677 layoutObject()->updateFromElement(); | 1677 layoutObject()->updateFromElement(); |
1678 } else if (timedelta > 3.0 && !m_sentStalledEvent) { | 1678 } else if (timedelta > 3.0 && !m_sentStalledEvent) { |
1679 scheduleEvent(EventTypeNames::stalled); | 1679 scheduleEvent(EventTypeNames::stalled); |
1680 m_sentStalledEvent = true; | 1680 m_sentStalledEvent = true; |
1681 setShouldDelayLoadEvent(false); | 1681 setShouldDelayLoadEvent(false); |
1682 } | 1682 } |
1683 } | 1683 } |
1684 | 1684 |
1685 void HTMLMediaElement::addPlayedRange(double start, double end) | 1685 void HTMLMediaElement::addPlayedRange(double start, double end) |
1686 { | 1686 { |
(...skipping 1239 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
2926 // Abort if duration unchanged. | 2926 // Abort if duration unchanged. |
2927 if (m_duration == duration) | 2927 if (m_duration == duration) |
2928 return; | 2928 return; |
2929 | 2929 |
2930 MEDIA_LOG << "durationChanged(" << (void*)this << ") : " << m_duration << "
-> " << duration; | 2930 MEDIA_LOG << "durationChanged(" << (void*)this << ") : " << m_duration << "
-> " << duration; |
2931 m_duration = duration; | 2931 m_duration = duration; |
2932 scheduleEvent(EventTypeNames::durationchange); | 2932 scheduleEvent(EventTypeNames::durationchange); |
2933 | 2933 |
2934 if (mediaControls()) | 2934 if (mediaControls()) |
2935 mediaControls()->reset(); | 2935 mediaControls()->reset(); |
2936 if (layoutObject()) | 2936 if (hasLayoutObject()) |
2937 layoutObject()->updateFromElement(); | 2937 layoutObject()->updateFromElement(); |
2938 | 2938 |
2939 if (requestSeek) | 2939 if (requestSeek) |
2940 seek(duration); | 2940 seek(duration); |
2941 } | 2941 } |
2942 | 2942 |
2943 void HTMLMediaElement::playbackStateChanged() | 2943 void HTMLMediaElement::playbackStateChanged() |
2944 { | 2944 { |
2945 MEDIA_LOG << "playbackStateChanged(" << (void*)this << ")"; | 2945 MEDIA_LOG << "playbackStateChanged(" << (void*)this << ")"; |
2946 | 2946 |
(...skipping 55 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
3002 startPlayerLoad(newUrl); | 3002 startPlayerLoad(newUrl); |
3003 } | 3003 } |
3004 | 3004 |
3005 // MediaPlayerPresentation methods | 3005 // MediaPlayerPresentation methods |
3006 void HTMLMediaElement::repaint() | 3006 void HTMLMediaElement::repaint() |
3007 { | 3007 { |
3008 if (m_webLayer) | 3008 if (m_webLayer) |
3009 m_webLayer->invalidate(); | 3009 m_webLayer->invalidate(); |
3010 | 3010 |
3011 updateDisplayState(); | 3011 updateDisplayState(); |
3012 if (layoutObject()) | 3012 if (hasLayoutObject()) |
3013 layoutObject()->setShouldDoFullPaintInvalidation(); | 3013 layoutObject()->setShouldDoFullPaintInvalidation(); |
3014 } | 3014 } |
3015 | 3015 |
3016 void HTMLMediaElement::sizeChanged() | 3016 void HTMLMediaElement::sizeChanged() |
3017 { | 3017 { |
3018 MEDIA_LOG << "sizeChanged(" << (void*)this << ")"; | 3018 MEDIA_LOG << "sizeChanged(" << (void*)this << ")"; |
3019 | 3019 |
3020 DCHECK(hasVideo()); // "resize" makes no sense in absence of video. | 3020 DCHECK(hasVideo()); // "resize" makes no sense in absence of video. |
3021 if (m_readyState > HAVE_NOTHING && isHTMLVideoElement()) | 3021 if (m_readyState > HAVE_NOTHING && isHTMLVideoElement()) |
3022 scheduleEvent(EventTypeNames::resize); | 3022 scheduleEvent(EventTypeNames::resize); |
3023 | 3023 |
3024 if (layoutObject()) | 3024 if (hasLayoutObject()) |
3025 layoutObject()->updateFromElement(); | 3025 layoutObject()->updateFromElement(); |
3026 } | 3026 } |
3027 | 3027 |
3028 TimeRanges* HTMLMediaElement::buffered() const | 3028 TimeRanges* HTMLMediaElement::buffered() const |
3029 { | 3029 { |
3030 if (m_mediaSource) | 3030 if (m_mediaSource) |
3031 return m_mediaSource->buffered(); | 3031 return m_mediaSource->buffered(); |
3032 | 3032 |
3033 if (!webMediaPlayer()) | 3033 if (!webMediaPlayer()) |
3034 return TimeRanges::create(); | 3034 return TimeRanges::create(); |
(...skipping 116 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
3151 m_playbackProgressTimer.stop(); | 3151 m_playbackProgressTimer.stop(); |
3152 m_playing = false; | 3152 m_playing = false; |
3153 double time = currentTime(); | 3153 double time = currentTime(); |
3154 if (time > m_lastSeekTime) | 3154 if (time > m_lastSeekTime) |
3155 addPlayedRange(m_lastSeekTime, time); | 3155 addPlayedRange(m_lastSeekTime, time); |
3156 | 3156 |
3157 if (mediaControls()) | 3157 if (mediaControls()) |
3158 mediaControls()->playbackStopped(); | 3158 mediaControls()->playbackStopped(); |
3159 } | 3159 } |
3160 | 3160 |
3161 if (layoutObject()) | 3161 if (hasLayoutObject()) |
3162 layoutObject()->updateFromElement(); | 3162 layoutObject()->updateFromElement(); |
3163 } | 3163 } |
3164 | 3164 |
3165 void HTMLMediaElement::stopPeriodicTimers() | 3165 void HTMLMediaElement::stopPeriodicTimers() |
3166 { | 3166 { |
3167 m_progressEventTimer.stop(); | 3167 m_progressEventTimer.stop(); |
3168 m_playbackProgressTimer.stop(); | 3168 m_playbackProgressTimer.stop(); |
3169 } | 3169 } |
3170 | 3170 |
3171 void HTMLMediaElement::clearMediaPlayerAndAudioSourceProviderClientWithoutLockin
g() | 3171 void HTMLMediaElement::clearMediaPlayerAndAudioSourceProviderClientWithoutLockin
g() |
(...skipping 23 matching lines...) Expand all Loading... |
3195 | 3195 |
3196 m_pendingActionFlags = 0; | 3196 m_pendingActionFlags = 0; |
3197 m_loadState = WaitingForSource; | 3197 m_loadState = WaitingForSource; |
3198 | 3198 |
3199 // We can't cast if we don't have a media player. | 3199 // We can't cast if we don't have a media player. |
3200 m_remoteRoutesAvailable = false; | 3200 m_remoteRoutesAvailable = false; |
3201 m_playingRemotely = false; | 3201 m_playingRemotely = false; |
3202 if (mediaControls()) | 3202 if (mediaControls()) |
3203 mediaControls()->refreshCastButtonVisibilityWithoutUpdate(); | 3203 mediaControls()->refreshCastButtonVisibilityWithoutUpdate(); |
3204 | 3204 |
3205 if (layoutObject()) | 3205 if (hasLayoutObject()) |
3206 layoutObject()->setShouldDoFullPaintInvalidation(); | 3206 layoutObject()->setShouldDoFullPaintInvalidation(); |
3207 } | 3207 } |
3208 | 3208 |
3209 void HTMLMediaElement::stop() | 3209 void HTMLMediaElement::stop() |
3210 { | 3210 { |
3211 MEDIA_LOG << "stop(" << (void*)this << ")"; | 3211 MEDIA_LOG << "stop(" << (void*)this << ")"; |
3212 | 3212 |
3213 // Close the async event queue so that no events are enqueued. | 3213 // Close the async event queue so that no events are enqueued. |
3214 cancelPendingEventsAndCallbacks(); | 3214 cancelPendingEventsAndCallbacks(); |
3215 m_asyncEventQueue->close(); | 3215 m_asyncEventQueue->close(); |
3216 | 3216 |
3217 // Clear everything in the Media Element | 3217 // Clear everything in the Media Element |
3218 clearMediaPlayer(); | 3218 clearMediaPlayer(); |
3219 m_readyState = HAVE_NOTHING; | 3219 m_readyState = HAVE_NOTHING; |
3220 m_readyStateMaximum = HAVE_NOTHING; | 3220 m_readyStateMaximum = HAVE_NOTHING; |
3221 setNetworkState(NETWORK_EMPTY); | 3221 setNetworkState(NETWORK_EMPTY); |
3222 setShouldDelayLoadEvent(false); | 3222 setShouldDelayLoadEvent(false); |
3223 m_currentSourceNode = nullptr; | 3223 m_currentSourceNode = nullptr; |
3224 invalidateCachedTime(); | 3224 invalidateCachedTime(); |
3225 cueTimeline().updateActiveCues(0); | 3225 cueTimeline().updateActiveCues(0); |
3226 m_playing = false; | 3226 m_playing = false; |
3227 m_paused = true; | 3227 m_paused = true; |
3228 m_seeking = false; | 3228 m_seeking = false; |
3229 | 3229 |
3230 if (layoutObject()) | 3230 if (hasLayoutObject()) |
3231 layoutObject()->updateFromElement(); | 3231 layoutObject()->updateFromElement(); |
3232 | 3232 |
3233 stopPeriodicTimers(); | 3233 stopPeriodicTimers(); |
3234 | 3234 |
3235 // Ensure that hasPendingActivity() is not preventing garbage collection, si
nce otherwise this | 3235 // Ensure that hasPendingActivity() is not preventing garbage collection, si
nce otherwise this |
3236 // media element will simply leak. | 3236 // media element will simply leak. |
3237 DCHECK(!hasPendingActivity()); | 3237 DCHECK(!hasPendingActivity()); |
3238 } | 3238 } |
3239 | 3239 |
3240 bool HTMLMediaElement::hasPendingActivity() const | 3240 bool HTMLMediaElement::hasPendingActivity() const |
(...skipping 787 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
4028 | 4028 |
4029 IntRect HTMLMediaElement::AutoplayHelperClientImpl::absoluteBoundingBoxRect() co
nst | 4029 IntRect HTMLMediaElement::AutoplayHelperClientImpl::absoluteBoundingBoxRect() co
nst |
4030 { | 4030 { |
4031 IntRect result; | 4031 IntRect result; |
4032 if (LayoutObject* object = m_element->layoutObject()) | 4032 if (LayoutObject* object = m_element->layoutObject()) |
4033 result = object->absoluteBoundingBoxRect(); | 4033 result = object->absoluteBoundingBoxRect(); |
4034 return result; | 4034 return result; |
4035 } | 4035 } |
4036 | 4036 |
4037 } // namespace blink | 4037 } // namespace blink |
OLD | NEW |