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