1# Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5import logging, time
6
7from autotest_lib.client.bin import utils
8from autotest_lib.client.common_lib import error
9from autotest_lib.client.cros.graphics import graphics_utils
10
11
12PLAYBACK_TEST_TIME_S = 10
13PLAYER_ENDED_STATE = 'Ended'
14PLAYER_PAUSE_STATE = 'Paused'
15PLAYER_PLAYING_STATE = 'Playing'
16WAIT_TIMEOUT_S = 20
17
18
19class YouTubeHelper(object):
20    """A helper class contains YouTube related utility functions.
21
22       To use this class, please call wait_for_player_state(playing) as below
23       before calling set_video_duration. Please note that set_video_duration
24       must be called in order to access few functions which uses the video
25       length member variable.
26
27       yh = youtube_helper.YouTubeHelper(tab)
28       yh.wait_for_player_state(PLAYER_PLAYING_STATE)
29       yh.set_video_duration()
30
31    """
32
33
34    def __init__(self, youtube_tab):
35        self._tab = youtube_tab
36        self._video_duration = 0
37
38
39    def set_video_duration(self):
40        """Sets the video duration."""
41        self._video_duration = (int(self._tab.EvaluateJavaScript(
42                'player.getDuration()')))
43
44
45    def video_current_time(self):
46        """Returns video's current playback time.
47
48        Returns:
49            returns the current playback location in seconds (int).
50
51        """
52        return int(self._tab.EvaluateJavaScript('player.getCurrentTime()'))
53
54
55    def get_player_status(self):
56        """Returns the player status."""
57        return self._tab.EvaluateJavaScript(
58                '(typeof playerStatus !== \'undefined\') && '
59                'playerStatus.innerHTML')
60
61
62    def set_playback_quality(self, quality):
63        """Set the video quality to the quality passed in the arg.
64
65        @param quality: video quality to set.
66
67        """
68        self._tab.ExecuteJavaScript(
69                'player.setPlaybackQuality("%s")' % quality)
70
71
72    def get_playback_quality(self):
73        """Returns the playback quality."""
74        return self._tab.EvaluateJavaScript('player.getPlaybackQuality()')
75
76
77    def wait_for_player_state(self, expected_status):
78        """Wait till the player status changes to expected_status.
79
80        If the status doesn't change for long, the test will time out after
81        WAIT_TIMEOUT_S and fails.
82
83        @param expected_status: status which is expected for the test
84                                to continue.
85
86        """
87        utils.poll_for_condition(
88            lambda: self.get_player_status() == expected_status,
89            exception=error.TestError(
90                        'Video failed to load. Player expected status: %s'
91                        ' and current status: %s.'
92                        % (expected_status, self.get_player_status())),
93            timeout=WAIT_TIMEOUT_S,
94            sleep_interval=1)
95
96    def verify_video_playback(self):
97        """Verify the video playback."""
98        logging.info('Verifying the YouTube video playback.')
99        playback = 0 # seconds
100        prev_playback = 0
101        count = 0
102        while (self.video_current_time() < self._video_duration
103               and playback < PLAYBACK_TEST_TIME_S):
104            time.sleep(1)
105            if self.video_current_time() <= prev_playback:
106                if count < 2:
107                    logging.info('Retrying to video playback test.')
108                    self._tab.ExecuteJavaScript(
109                            'player.seekTo(%d, true)'
110                            % (self.video_current_time() + 2))
111                    time.sleep(1)
112                    count = count + 1
113                else:
114                    player_status = self.get_player_status()
115                    raise error.TestError(
116                            'Video is not playing. Player status: %s.' %
117                            player_status)
118            prev_playback = self.video_current_time()
119            playback = playback + 1
120
121
122    def wait_for_expected_resolution(self, expected_quality):
123        """Wait for some time for getting expected resolution.
124
125        @param expected_quality: quality to be set.
126
127        """
128        for _ in range(WAIT_TIMEOUT_S):
129            dummy_quality = self.get_playback_quality()
130            if dummy_quality == expected_quality:
131                logging.info('Expected resolution is set.')
132                break
133            else:
134                logging.info('Waiting for expected resolution.')
135                time.sleep(0.1)
136
137
138    def verify_video_resolutions(self):
139        """Verify available video resolutions.
140
141        Video resolution should be 360p, 480p, 720p and 1080p.
142
143        """
144        logging.info('Verifying the video resolutions.')
145        video_qualities = self._tab.EvaluateJavaScript(
146                'player.getAvailableQualityLevels()')
147        logging.info('Available video resolutions: %s', video_qualities)
148        if not video_qualities:
149            raise error.TestError(
150                    'Player failed to return available video qualities.')
151        video_qualities.reverse()
152        width, height = graphics_utils.get_internal_resolution()
153        logging.info('checking resolution: %d width  %d height', width, height)
154        for quality in video_qualities:
155            logging.info('Playing video in %s quality.', quality)
156            self.set_playback_quality(quality)
157            self.wait_for_player_state(PLAYER_PLAYING_STATE)
158            self.wait_for_expected_resolution(quality)
159            current_quality = self.get_playback_quality()
160            if (quality not in ['auto', 'tiny', 'small'] and
161                    quality != current_quality):
162                if current_quality in ['hd720', 'hd1080'] and width == 2560:
163                      logging.info('HD devices starts playing YouTube video in '
164                                   'HD so skipping 480p test.')
165                      continue
166                raise error.TestError(
167                        'Expected video quality: %s. Current video quality: %s'
168                        % (quality, current_quality))
169            time.sleep(1)
170        # setting the video resolution to 480p for rest of the tests.
171        self.set_playback_quality('large')
172
173
174    def verify_player_states(self):
175        """Verify the player states like play, pause, ended and seek."""
176        logging.info('Verifying the player states.')
177        self._tab.ExecuteJavaScript('player.pauseVideo()')
178        self.wait_for_player_state(PLAYER_PAUSE_STATE)
179        self._tab.ExecuteJavaScript('player.playVideo()')
180        self.wait_for_player_state(PLAYER_PLAYING_STATE)
181        # We are seeking the player position to (video length - 2 seconds).
182        # Since the player waits for WAIT_TIMEOUT_S for the status change,
183        # the video should be ended before we hit the timeout.
184        video_end_test_duration = (self._video_duration -
185                                   self.video_current_time() - 2)
186        if video_end_test_duration >= WAIT_TIMEOUT_S:
187            self._tab.ExecuteJavaScript(
188                    'player.seekTo(%d, true)' % (self._video_duration - 5))
189            self.wait_for_player_state(PLAYER_ENDED_STATE)
190        else:
191            raise error.TestError(
192                    'Test video is not long enough for the video end test.')
193        # Verifying seek back from the end position.
194        self._tab.ExecuteJavaScript('player.seekTo(%d, true)'
195                                    % (self._video_duration / 2))
196        self.wait_for_player_state(PLAYER_PLAYING_STATE)
197        # So the playback doesn't stay at the mid.
198        seek_test = False
199        for _ in range(WAIT_TIMEOUT_S):
200            logging.info('Waiting for seek position to change.')
201            time.sleep(1)
202            seek_position = self.video_current_time()
203            if (seek_position > self._video_duration / 2
204                    and seek_position < self._video_duration):
205                seek_test = True
206                break
207        if not seek_test:
208            raise error.TestError(
209                    'Seek location is wrong. '
210                    'Video length: %d, seek position: %d.' %
211                    (self._video_duration, seek_position))
212