1 | /* |
2 | Copyright (C) 2012 Trever Fischer <tdfischer@fedoraproject.org> |
3 | Copyright (C) 2012 Harald Sitter <sitter@kde.org> |
4 | |
5 | This library is free software; you can redistribute it and/or |
6 | modify it under the terms of the GNU Lesser General Public |
7 | License as published by the Free Software Foundation; either |
8 | version 2.1 of the License, or (at your option) version 3, or any |
9 | later version accepted by the membership of KDE e.V. (or its |
10 | successor approved by the membership of KDE e.V.), Nokia Corporation |
11 | (or its successors, if any) and the KDE Free Qt Foundation, which shall |
12 | act as a proxy defined in Section 6 of version 3 of the license. |
13 | |
14 | This library is distributed in the hope that it will be useful, |
15 | but WITHOUT ANY WARRANTY; without even the implied warranty of |
16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
17 | Lesser General Public License for more details. |
18 | |
19 | You should have received a copy of the GNU Lesser General Public |
20 | License along with this library. If not, see <http://www.gnu.org/licenses/>. |
21 | */ |
22 | |
23 | #include "statesvalidator_p.h" |
24 | |
25 | #include "mediaobject.h" |
26 | #include "phononnamespace_p.h" |
27 | |
28 | #define P_INVALID_STATE(msg) Q_ASSERT_X(0, __FILE__, msg) |
29 | |
30 | namespace Phonon |
31 | { |
32 | |
33 | StatesValidator::StatesValidator(MediaObject *parent) |
34 | : QObject(parent) |
35 | , m_mediaObject(parent) |
36 | , m_prevState(Phonon::ErrorState) |
37 | , m_sourceQueued(false) |
38 | , m_aboutToFinishEmitted(false) |
39 | , m_aboutToFinishBeforeSeek(false) |
40 | , m_aboutToFinishPos(-1) |
41 | { |
42 | connect(sender: m_mediaObject, SIGNAL(stateChanged(Phonon::State,Phonon::State)), |
43 | receiver: this, SLOT(validateStateChange(Phonon::State,Phonon::State))); |
44 | connect(sender: m_mediaObject, SIGNAL(currentSourceChanged(Phonon::MediaSource)), |
45 | receiver: this, SLOT(validateSourceChange())); |
46 | connect(sender: m_mediaObject, SIGNAL(tick(qint64)), receiver: this, SLOT(validateTick(qint64))); |
47 | connect(sender: m_mediaObject, SIGNAL(aboutToFinish()), receiver: this, SLOT(validateAboutToFinish())); |
48 | connect(sender: m_mediaObject, SIGNAL(finished()), receiver: this, SLOT(validateFinished())); |
49 | connect(sender: m_mediaObject, SIGNAL(bufferStatus(int)), receiver: this, SLOT(validateBufferStatus())); |
50 | } |
51 | |
52 | StatesValidator::~StatesValidator() |
53 | { |
54 | } |
55 | |
56 | /** |
57 | * The aboutToFinish signal is emitted when the queue is coming to an end. |
58 | * This in particular means that it must not be emitted twice, unless no track |
59 | * is in the queue and the user seeked back in time before finished |
60 | * Since we track the frontend signal here, we only get this signal when the |
61 | * queue is in fact empty in the frontend. |
62 | * It can however happen that the frontend already delivered the last queue item, |
63 | * then the user seeks and the backend forgets to use the already delivered item, |
64 | * emitting a bogus aboutToFinish. |
65 | */ |
66 | void StatesValidator::validateAboutToFinish() |
67 | { |
68 | if (m_aboutToFinishEmitted) |
69 | P_INVALID_STATE("aboutToFinish emitted more than once!" ); |
70 | m_aboutToFinishEmitted = true; |
71 | m_aboutToFinishPos = m_pos; |
72 | } |
73 | |
74 | void StatesValidator::validateFinished() |
75 | { |
76 | if (m_mediaObject->state() != Phonon::PlayingState) |
77 | P_INVALID_STATE("Playback finished when we weren't playing!" ); |
78 | } |
79 | |
80 | void StatesValidator::validateSourceChange() |
81 | { |
82 | if (m_mediaObject->state() != Phonon::StoppedState |
83 | && m_mediaObject->state() != Phonon::PlayingState |
84 | && m_mediaObject->state() != Phonon::PausedState |
85 | && m_mediaObject->state() != Phonon::BufferingState) { |
86 | P_INVALID_STATE("Source got changed outside a valid state" ); |
87 | } |
88 | m_sourceQueued = false; // Once we get the signal the backend internal one-source queue is definitely cleared. |
89 | m_aboutToFinishEmitted = false; |
90 | m_aboutToFinishBeforeSeek = false; |
91 | } |
92 | |
93 | void StatesValidator::validateBufferStatus() |
94 | { |
95 | if (m_mediaObject->state() != Phonon::PlayingState |
96 | && m_mediaObject->state() != Phonon::PausedState |
97 | && m_mediaObject->state() != Phonon::BufferingState) { |
98 | P_INVALID_STATE("Buffer status changed when we weren't supposed to be buffering" ); |
99 | } |
100 | } |
101 | |
102 | void StatesValidator::validateTick(qint64 pos) |
103 | { |
104 | // Mind that Buffering is a concurrent state, you may have been playing and |
105 | // then start buffering for example for a seek. |
106 | if (m_mediaObject->state() != Phonon::PlayingState |
107 | && (m_prevState != Phonon::PlayingState |
108 | && m_mediaObject->state() != Phonon::BufferingState)) |
109 | P_INVALID_STATE("Received tick outside of Playing state." ); |
110 | // If and only if we did not queue a new source may a seek back in time |
111 | // result in a reemission of the signal. It should not, but it is allowed. |
112 | // Point being, if the API consumer did not set one the first time, they |
113 | // likely will not care about it a second time either. |
114 | if (m_aboutToFinishEmitted && (pos < m_aboutToFinishPos) && !m_sourceQueued) |
115 | m_aboutToFinishEmitted = false; |
116 | m_pos = pos; |
117 | } |
118 | |
119 | void StatesValidator::validateStateChange(Phonon::State newstate, Phonon::State oldstate) |
120 | { |
121 | if (!validateStateTransition(newstate, oldstate)) { |
122 | pDebug() << "Invalid state transition:" << oldstate << "->" << newstate; |
123 | P_INVALID_STATE("Invalid state transition" ); |
124 | } else { |
125 | pDebug() << "Valid state transition:" << oldstate << "->" << newstate; |
126 | } |
127 | m_prevState = oldstate; |
128 | } |
129 | |
130 | bool StatesValidator::validateStateTransition(Phonon::State newstate, Phonon::State oldstate) |
131 | { |
132 | // Non-playback states trigger a reset of aboutToFinish. |
133 | switch (oldstate) { |
134 | case Phonon::StoppedState: |
135 | switch (newstate) { |
136 | case Phonon::LoadingState: |
137 | case Phonon::PlayingState: |
138 | case Phonon::PausedState: |
139 | return true; |
140 | default: |
141 | return false; |
142 | } |
143 | break; |
144 | case Phonon::LoadingState: |
145 | switch (newstate) { |
146 | case Phonon::ErrorState: |
147 | case Phonon::StoppedState: |
148 | return true; |
149 | default: |
150 | return false; |
151 | } |
152 | break; |
153 | case Phonon::ErrorState: |
154 | switch (newstate) { |
155 | case Phonon::LoadingState: |
156 | return true; |
157 | default: |
158 | return false; |
159 | } |
160 | break; |
161 | case Phonon::PlayingState: |
162 | switch (newstate) { |
163 | case Phonon::PausedState: |
164 | case Phonon::BufferingState: |
165 | case Phonon::ErrorState: |
166 | case Phonon::StoppedState: |
167 | return true; |
168 | default: |
169 | return false; |
170 | } |
171 | break; |
172 | case Phonon::PausedState: |
173 | switch (newstate) { |
174 | case Phonon::PlayingState: |
175 | case Phonon::BufferingState: |
176 | case Phonon::ErrorState: |
177 | case Phonon::StoppedState: |
178 | return true; |
179 | default: |
180 | return false; |
181 | } |
182 | break; |
183 | case Phonon::BufferingState: |
184 | switch (newstate) { |
185 | case Phonon::PlayingState: |
186 | case Phonon::PausedState: |
187 | case Phonon::ErrorState: |
188 | // TODO: buffering state needs fixing, should not transit to stop |
189 | case Phonon::StoppedState: |
190 | return true; |
191 | default: |
192 | return false; |
193 | } |
194 | } |
195 | return false; |
196 | } |
197 | |
198 | } // namespace Phonon |
199 | |
200 | |