OLD | NEW |
| (Empty) |
1 // Copyright 2016 The Chromium 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 | |
5 #include "media/filters/pipeline_controller.h" | |
6 | |
7 #include "base/bind.h" | |
8 #include "base/bind_helpers.h" | |
9 #include "media/base/bind_to_current_loop.h" | |
10 #include "media/filters/chunk_demuxer.h" | |
11 | |
12 namespace media { | |
13 | |
14 PipelineController::PipelineController( | |
15 Pipeline* pipeline, | |
16 const RendererFactoryCB& renderer_factory_cb, | |
17 const SeekedCB& seeked_cb, | |
18 const SuspendedCB& suspended_cb, | |
19 const ResumedCB& resumed_cb, | |
20 const PipelineStatusCB& error_cb) | |
21 : pipeline_(pipeline), | |
22 renderer_factory_cb_(renderer_factory_cb), | |
23 seeked_cb_(seeked_cb), | |
24 suspended_cb_(suspended_cb), | |
25 resumed_cb_(resumed_cb), | |
26 error_cb_(error_cb), | |
27 weak_factory_(this) { | |
28 DCHECK(pipeline_); | |
29 DCHECK(!renderer_factory_cb_.is_null()); | |
30 DCHECK(!seeked_cb_.is_null()); | |
31 DCHECK(!suspended_cb_.is_null()); | |
32 DCHECK(!resumed_cb_.is_null()); | |
33 DCHECK(!error_cb_.is_null()); | |
34 } | |
35 | |
36 PipelineController::~PipelineController() { | |
37 DCHECK(thread_checker_.CalledOnValidThread()); | |
38 } | |
39 | |
40 // TODO(sandersd): Move ChunkDemuxer API to Demuxer so that Pipeline can | |
41 // implement all of this. | |
42 // TODO(sandersd): If there is a pending suspend, don't call pipeline_.Start() | |
43 // until Resume(). | |
44 void PipelineController::Start( | |
45 ChunkDemuxer* chunk_demuxer, | |
46 Demuxer* demuxer, | |
47 bool is_streaming, | |
48 const base::Closure& ended_cb, | |
49 const PipelineMetadataCB& metadata_cb, | |
50 const BufferingStateCB& buffering_state_cb, | |
51 const base::Closure& duration_change_cb, | |
52 const AddTextTrackCB& add_text_track_cb, | |
53 const base::Closure& waiting_for_decryption_key_cb) { | |
54 DCHECK(thread_checker_.CalledOnValidThread()); | |
55 DCHECK(state_ == State::CREATED); | |
56 | |
57 if (chunk_demuxer) | |
58 DCHECK_EQ(demuxer, chunk_demuxer); | |
59 | |
60 // Once the pipeline is started, we want to call the seeked callback but | |
61 // without a time update. | |
62 pending_seeked_cb_ = true; | |
63 state_ = State::STARTING; | |
64 | |
65 chunk_demuxer_ = chunk_demuxer; | |
66 is_streaming_ = is_streaming; | |
67 pipeline_->Start( | |
68 demuxer, renderer_factory_cb_.Run(), ended_cb, | |
69 BindToCurrentLoop(error_cb_), | |
70 BindToCurrentLoop(base::Bind(&PipelineController::OnPipelineStatus, | |
71 weak_factory_.GetWeakPtr(), State::PLAYING)), | |
72 metadata_cb, buffering_state_cb, duration_change_cb, add_text_track_cb, | |
73 waiting_for_decryption_key_cb); | |
74 } | |
75 | |
76 void PipelineController::Seek(base::TimeDelta time, bool time_updated) { | |
77 DCHECK(thread_checker_.CalledOnValidThread()); | |
78 | |
79 // It would be slightly more clear to set this in Dispatch(), but we want to | |
80 // be sure it gets updated even if the seek is elided. | |
81 if (time_updated) | |
82 pending_time_updated_ = true; | |
83 pending_seeked_cb_ = true; | |
84 | |
85 // If we are already seeking to |time|, just clear any pending seek. This does | |
86 // not apply to MSE because the underlying buffer could have been changed | |
87 // between the seek calls. | |
88 // TODO(sandersd): The underlying buffer could also have changed for | |
89 // File objects, but WMPI is also broken in that case (because it caches). | |
90 if ((state_ == State::SEEKING || state_ == State::RESUMING) && | |
91 seek_time_ == time && !chunk_demuxer_) { | |
92 pending_seek_ = false; | |
93 return; | |
94 } | |
95 | |
96 pending_seek_time_ = time; | |
97 pending_seek_ = true; | |
98 Dispatch(); | |
99 } | |
100 | |
101 // TODO(sandersd): It may be easier to use this interface if |suspended_cb_| is | |
102 // executed when Suspend() is called while already suspended. | |
103 void PipelineController::Suspend() { | |
104 DCHECK(thread_checker_.CalledOnValidThread()); | |
105 pending_resume_ = false; | |
106 if (state_ != State::SUSPENDING && state_ != State::SUSPENDED) { | |
107 pending_suspend_ = true; | |
108 Dispatch(); | |
109 } | |
110 } | |
111 | |
112 void PipelineController::Resume() { | |
113 DCHECK(thread_checker_.CalledOnValidThread()); | |
114 pending_suspend_ = false; | |
115 if (state_ == State::SUSPENDING || state_ == State::SUSPENDED) { | |
116 pending_resume_ = true; | |
117 Dispatch(); | |
118 } | |
119 } | |
120 | |
121 bool PipelineController::IsStable() { | |
122 DCHECK(thread_checker_.CalledOnValidThread()); | |
123 return (state_ == State::PLAYING); | |
124 } | |
125 | |
126 bool PipelineController::IsSuspended() { | |
127 DCHECK(thread_checker_.CalledOnValidThread()); | |
128 return (state_ == State::SUSPENDED); | |
129 } | |
130 | |
131 void PipelineController::OnPipelineStatus(State state, | |
132 PipelineStatus pipeline_status) { | |
133 DCHECK(thread_checker_.CalledOnValidThread()); | |
134 | |
135 if (pipeline_status != PIPELINE_OK) { | |
136 error_cb_.Run(pipeline_status); | |
137 return; | |
138 } | |
139 | |
140 state_ = state; | |
141 | |
142 if (state == State::PLAYING) { | |
143 // Start(), Seek(), or Resume() completed; we can be sure that | |
144 // |chunk_demuxer_| got the seek it was waiting for. | |
145 waiting_for_seek_ = false; | |
146 if (pending_resumed_cb_) { | |
147 pending_resumed_cb_ = false; | |
148 | |
149 // Warning: possibly reentrant. The state may change inside this callback. | |
150 // It must be safe to call Dispatch() twice in a row here. | |
151 resumed_cb_.Run(); | |
152 } | |
153 } else if (state == State::SUSPENDED) { | |
154 pending_resumed_cb_ = true; | |
155 | |
156 // Warning: possibly reentrant. The state may change inside this callback. | |
157 // It must be safe to call Dispatch() twice in a row here. | |
158 suspended_cb_.Run(); | |
159 } | |
160 | |
161 Dispatch(); | |
162 } | |
163 | |
164 // Note: Dispatch() may be called re-entrantly (by callbacks internally) or | |
165 // twice in a row (by OnPipelineStatus()). | |
166 void PipelineController::Dispatch() { | |
167 DCHECK(thread_checker_.CalledOnValidThread()); | |
168 | |
169 // Suspend/resume transitions take priority because seeks before a suspend | |
170 // are wasted, and seeks after can be merged into the resume operation. | |
171 if (pending_suspend_ && state_ == State::PLAYING) { | |
172 pending_suspend_ = false; | |
173 state_ = State::SUSPENDING; | |
174 pipeline_->Suspend(BindToCurrentLoop( | |
175 base::Bind(&PipelineController::OnPipelineStatus, | |
176 weak_factory_.GetWeakPtr(), State::SUSPENDED))); | |
177 return; | |
178 } | |
179 | |
180 if (pending_resume_ && state_ == State::SUSPENDED) { | |
181 // If there is a pending seek, resume to that time instead... | |
182 if (pending_seek_) { | |
183 seek_time_ = pending_seek_time_; | |
184 pending_seek_ = false; | |
185 } else { | |
186 seek_time_ = pipeline_->GetMediaTime(); | |
187 } | |
188 | |
189 // ...unless the media is streaming, in which case we resume at the start | |
190 // because seeking doesn't work well. | |
191 if (is_streaming_ && !seek_time_.is_zero()) { | |
192 seek_time_ = base::TimeDelta(); | |
193 | |
194 // In this case we want to make sure that the controls get updated | |
195 // immediately, so we don't try to hide the seek. | |
196 pending_time_updated_ = true; | |
197 } | |
198 | |
199 // Tell |chunk_demuxer_| to expect our resume. | |
200 if (chunk_demuxer_) { | |
201 DCHECK(!waiting_for_seek_); | |
202 chunk_demuxer_->StartWaitingForSeek(seek_time_); | |
203 waiting_for_seek_ = true; | |
204 } | |
205 | |
206 pending_resume_ = false; | |
207 state_ = State::RESUMING; | |
208 pipeline_->Resume(renderer_factory_cb_.Run(), seek_time_, | |
209 BindToCurrentLoop(base::Bind( | |
210 &PipelineController::OnPipelineStatus, | |
211 weak_factory_.GetWeakPtr(), State::PLAYING))); | |
212 return; | |
213 } | |
214 | |
215 // |chunk_demuxer_| supports aborting seeks. Make use of that when we have | |
216 // other pending operations. | |
217 if ((pending_seek_ || pending_suspend_) && waiting_for_seek_) { | |
218 CHECK(chunk_demuxer_); | |
219 | |
220 // If there is no pending seek, return the current seek to pending status. | |
221 if (!pending_seek_) { | |
222 pending_seek_time_ = seek_time_; | |
223 pending_seek_ = true; | |
224 } | |
225 | |
226 // CancelPendingSeek() may be reentrant, so update state first and return | |
227 // immediately. | |
228 waiting_for_seek_ = false; | |
229 chunk_demuxer_->CancelPendingSeek(pending_seek_time_); | |
230 return; | |
231 } | |
232 | |
233 // Ordinary seeking. | |
234 if (pending_seek_ && state_ == State::PLAYING) { | |
235 seek_time_ = pending_seek_time_; | |
236 | |
237 // Tell |chunk_demuxer_| to expect our seek. | |
238 if (chunk_demuxer_) { | |
239 DCHECK(!waiting_for_seek_); | |
240 waiting_for_seek_ = true; | |
241 chunk_demuxer_->StartWaitingForSeek(seek_time_); | |
242 } | |
243 | |
244 pending_seek_ = false; | |
245 state_ = State::SEEKING; | |
246 pipeline_->Seek(seek_time_, | |
247 BindToCurrentLoop(base::Bind( | |
248 &PipelineController::OnPipelineStatus, | |
249 weak_factory_.GetWeakPtr(), State::PLAYING))); | |
250 return; | |
251 } | |
252 | |
253 // If |state_| is PLAYING and we didn't trigger an operation above then we | |
254 // are in a stable state. If there is a seeked callback pending, emit it. | |
255 if (state_ == State::PLAYING) { | |
256 if (pending_seeked_cb_) { | |
257 // |seeked_cb_| may be reentrant, so update state first and return | |
258 // immediately. | |
259 pending_seeked_cb_ = false; | |
260 bool was_pending_time_updated = pending_time_updated_; | |
261 pending_time_updated_ = false; | |
262 seeked_cb_.Run(was_pending_time_updated); | |
263 return; | |
264 } | |
265 } | |
266 } | |
267 | |
268 } // namespace media | |
OLD | NEW |