View Javadoc
1   /*
2    * Copyright (C) 2013 4th Line GmbH, Switzerland
3    *
4    * The contents of this file are subject to the terms of either the GNU
5    * Lesser General Public License Version 2 or later ("LGPL") or the
6    * Common Development and Distribution License Version 1 or later
7    * ("CDDL") (collectively, the "License"). You may not use this file
8    * except in compliance with the License. See LICENSE.txt for more
9    * information.
10   *
11   * This program is distributed in the hope that it will be useful,
12   * but WITHOUT ANY WARRANTY; without even the implied warranty of
13   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
14   */
15  
16  package org.fourthline.cling.support.avtransport.impl;
17  
18  import org.fourthline.cling.model.types.ErrorCode;
19  import org.fourthline.cling.model.types.UnsignedIntegerFourBytes;
20  import org.fourthline.cling.support.avtransport.AVTransportErrorCode;
21  import org.fourthline.cling.support.avtransport.AVTransportException;
22  import org.fourthline.cling.support.avtransport.AbstractAVTransportService;
23  import org.fourthline.cling.support.avtransport.impl.state.AbstractState;
24  import org.fourthline.cling.support.lastchange.LastChange;
25  import org.fourthline.cling.support.model.AVTransport;
26  import org.fourthline.cling.support.model.DeviceCapabilities;
27  import org.fourthline.cling.support.model.MediaInfo;
28  import org.fourthline.cling.support.model.PlayMode;
29  import org.fourthline.cling.support.model.PositionInfo;
30  import org.fourthline.cling.support.model.RecordQualityMode;
31  import org.fourthline.cling.support.model.SeekMode;
32  import org.fourthline.cling.support.model.StorageMedium;
33  import org.fourthline.cling.support.model.TransportAction;
34  import org.fourthline.cling.support.model.TransportInfo;
35  import org.fourthline.cling.support.model.TransportSettings;
36  import org.seamless.statemachine.StateMachineBuilder;
37  import org.seamless.statemachine.TransitionException;
38  
39  import java.net.URI;
40  import java.util.Map;
41  import java.util.concurrent.ConcurrentHashMap;
42  import java.util.logging.Logger;
43  
44  /**
45   * State-machine based implementation of AVTransport service.
46   * <p>
47   * One logical AVTransport is represented by:
48   * </p>
49   * <ul>
50   * <li>
51   * One {@link org.fourthline.cling.support.avtransport.impl.AVTransportStateMachine}
52   * instance that accepts the action method call as a proxy.
53   * </li>
54   * <li>
55   * Each state machine holds several instances of
56   * {@link org.fourthline.cling.support.avtransport.impl.state.AbstractState}, created on
57   * instantation of the state machine. The "current" state will be the target of
58   * the action call. It is the state implementation that decides how to handle the
59   * call and what the next state is after a possible transition.
60   * </li>
61   * <li>
62   * Each state has a reference to an implementation of
63   * {@link org.fourthline.cling.support.model.AVTransport}, where the state can hold
64   * information about well, the state.
65   * </li>
66   * </ul>
67   * <p>
68   * Simplified, this means that each AVTransport instance ID is typically handled by
69   * one state machine, and the internal state of that machine is stored in an
70   * <code>AVTransport</code>.
71   * </p>
72   * <p>
73   * Override the {@link #createTransport(org.fourthline.cling.model.types.UnsignedIntegerFourBytes, org.fourthline.cling.support.lastchange.LastChange)}
74   * method to utilize a subclass of <code>AVTransport</code> as your internal state holder.
75   * </p>
76   *
77   * @author Christian Bauer
78   */
79  public class AVTransportService<T extends AVTransport> extends AbstractAVTransportService {
80  
81      final private static Logger log = Logger.getLogger(AVTransportService.class.getName());
82  
83      final private Map<Long, AVTransportStateMachine> stateMachines = new ConcurrentHashMap();
84  
85      final Class<? extends AVTransportStateMachine> stateMachineDefinition;
86      final Class<? extends AbstractState> initialState;
87      final Class<? extends AVTransport> transportClass;
88  
89      public AVTransportService(Class<? extends AVTransportStateMachine> stateMachineDefinition,
90                                Class<? extends AbstractState> initialState) {
91          this(stateMachineDefinition, initialState, (Class<T>)AVTransport.class);
92      }
93  
94      public AVTransportService(Class<? extends AVTransportStateMachine> stateMachineDefinition,
95                                Class<? extends AbstractState> initialState,
96                                Class<T> transportClass) {
97          this.stateMachineDefinition = stateMachineDefinition;
98          this.initialState = initialState;
99          this.transportClass = transportClass;
100     }
101 
102     public void setAVTransportURI(UnsignedIntegerFourBytes instanceId,
103                                   String currentURI,
104                                   String currentURIMetaData) throws AVTransportException {
105 
106         URI uri;
107         try {
108             uri = new URI(currentURI);
109         } catch (Exception ex) {
110             throw new AVTransportException(
111                     ErrorCode.INVALID_ARGS, "CurrentURI can not be null or malformed"
112             );
113         }
114 
115         try {
116             AVTransportStateMachine transportStateMachine = findStateMachine(instanceId, true);
117             transportStateMachine.setTransportURI(uri, currentURIMetaData);
118         } catch (TransitionException ex) {
119             throw new AVTransportException(AVTransportErrorCode.TRANSITION_NOT_AVAILABLE, ex.getMessage());
120         }
121     }
122 
123     public void setNextAVTransportURI(UnsignedIntegerFourBytes instanceId,
124                                       String nextURI,
125                                       String nextURIMetaData) throws AVTransportException {
126 
127         URI uri;
128         try {
129             uri = new URI(nextURI);
130         } catch (Exception ex) {
131             throw new AVTransportException(
132                     ErrorCode.INVALID_ARGS, "NextURI can not be null or malformed"
133             );
134         }
135 
136         try {
137             AVTransportStateMachine transportStateMachine = findStateMachine(instanceId, true);
138             transportStateMachine.setNextTransportURI(uri, nextURIMetaData);
139         } catch (TransitionException ex) {
140             throw new AVTransportException(AVTransportErrorCode.TRANSITION_NOT_AVAILABLE, ex.getMessage());
141         }
142     }
143 
144     public void setPlayMode(UnsignedIntegerFourBytes instanceId, String newPlayMode) throws AVTransportException {
145         AVTransport transport = findStateMachine(instanceId).getCurrentState().getTransport();
146         try {
147             transport.setTransportSettings(
148                     new TransportSettings(
149                             PlayMode.valueOf(newPlayMode),
150                             transport.getTransportSettings().getRecQualityMode()
151                     )
152             );
153         } catch (IllegalArgumentException ex) {
154             throw new AVTransportException(
155                     AVTransportErrorCode.PLAYMODE_NOT_SUPPORTED, "Unsupported play mode: " + newPlayMode
156             );
157         }
158     }
159 
160     public void setRecordQualityMode(UnsignedIntegerFourBytes instanceId, String newRecordQualityMode) throws AVTransportException {
161         AVTransport transport = findStateMachine(instanceId).getCurrentState().getTransport();
162         try {
163             transport.setTransportSettings(
164                     new TransportSettings(
165                             transport.getTransportSettings().getPlayMode(),
166                             RecordQualityMode.valueOrExceptionOf(newRecordQualityMode)
167                     )
168             );
169         } catch (IllegalArgumentException ex) {
170             throw new AVTransportException(
171                     AVTransportErrorCode.RECORDQUALITYMODE_NOT_SUPPORTED, "Unsupported record quality mode: " + newRecordQualityMode
172             );
173         }
174     }
175 
176     public MediaInfo getMediaInfo(UnsignedIntegerFourBytes instanceId) throws AVTransportException {
177         return findStateMachine(instanceId).getCurrentState().getTransport().getMediaInfo();
178     }
179 
180     public TransportInfo getTransportInfo(UnsignedIntegerFourBytes instanceId) throws AVTransportException {
181         return findStateMachine(instanceId).getCurrentState().getTransport().getTransportInfo();
182     }
183 
184     public PositionInfo getPositionInfo(UnsignedIntegerFourBytes instanceId) throws AVTransportException {
185         return findStateMachine(instanceId).getCurrentState().getTransport().getPositionInfo();
186     }
187 
188     public DeviceCapabilities getDeviceCapabilities(UnsignedIntegerFourBytes instanceId) throws AVTransportException {
189         return findStateMachine(instanceId).getCurrentState().getTransport().getDeviceCapabilities();
190     }
191 
192     public TransportSettings getTransportSettings(UnsignedIntegerFourBytes instanceId) throws AVTransportException {
193         return findStateMachine(instanceId).getCurrentState().getTransport().getTransportSettings();
194     }
195 
196     public void stop(UnsignedIntegerFourBytes instanceId) throws AVTransportException {
197         try {
198             findStateMachine(instanceId).stop();
199         } catch (TransitionException ex) {
200             throw new AVTransportException(AVTransportErrorCode.TRANSITION_NOT_AVAILABLE, ex.getMessage());
201         }
202     }
203 
204     public void play(UnsignedIntegerFourBytes instanceId, String speed) throws AVTransportException {
205         try {
206             findStateMachine(instanceId).play(speed);
207         } catch (TransitionException ex) {
208             throw new AVTransportException(AVTransportErrorCode.TRANSITION_NOT_AVAILABLE, ex.getMessage());
209         }
210     }
211 
212     public void pause(UnsignedIntegerFourBytes instanceId) throws AVTransportException {
213         try {
214             findStateMachine(instanceId).pause();
215         } catch (TransitionException ex) {
216             throw new AVTransportException(AVTransportErrorCode.TRANSITION_NOT_AVAILABLE, ex.getMessage());
217         }
218     }
219 
220     public void record(UnsignedIntegerFourBytes instanceId) throws AVTransportException {
221         try {
222             findStateMachine(instanceId).record();
223         } catch (TransitionException ex) {
224             throw new AVTransportException(AVTransportErrorCode.TRANSITION_NOT_AVAILABLE, ex.getMessage());
225         }
226     }
227 
228     public void seek(UnsignedIntegerFourBytes instanceId, String unit, String target) throws AVTransportException {
229         SeekMode seekMode;
230         try {
231              seekMode = SeekMode.valueOrExceptionOf(unit);
232         } catch (IllegalArgumentException ex) {
233             throw new AVTransportException(
234                     AVTransportErrorCode.SEEKMODE_NOT_SUPPORTED, "Unsupported seek mode: " + unit
235             );
236         }
237 
238         try {
239             findStateMachine(instanceId).seek(seekMode, target);
240         } catch (TransitionException ex) {
241             throw new AVTransportException(AVTransportErrorCode.TRANSITION_NOT_AVAILABLE, ex.getMessage());
242         }
243     }
244 
245     public void next(UnsignedIntegerFourBytes instanceId) throws AVTransportException {
246         try {
247             findStateMachine(instanceId).next();
248         } catch (TransitionException ex) {
249             throw new AVTransportException(AVTransportErrorCode.TRANSITION_NOT_AVAILABLE, ex.getMessage());
250         }
251     }
252 
253     public void previous(UnsignedIntegerFourBytes instanceId) throws AVTransportException {
254         try {
255             findStateMachine(instanceId).previous();
256         } catch (TransitionException ex) {
257             throw new AVTransportException(AVTransportErrorCode.TRANSITION_NOT_AVAILABLE, ex.getMessage());
258         }
259     }
260 
261     @Override
262     protected TransportAction[] getCurrentTransportActions(UnsignedIntegerFourBytes instanceId) throws Exception {
263         AVTransportStateMachine stateMachine = findStateMachine(instanceId);
264         try {
265             return stateMachine.getCurrentState().getCurrentTransportActions();
266         } catch (TransitionException ex) {
267             return new TransportAction[0];
268         }
269     }
270 
271     @Override
272     public UnsignedIntegerFourBytes[] getCurrentInstanceIds() {
273         synchronized (stateMachines) {
274             UnsignedIntegerFourBytes[] ids = new UnsignedIntegerFourBytes[stateMachines.size()];
275             int i = 0;
276             for (Long id : stateMachines.keySet()) {
277                 ids[i] = new UnsignedIntegerFourBytes(id);
278                 i++;
279             }
280             return ids;
281         }
282     }
283 
284     protected AVTransportStateMachine findStateMachine(UnsignedIntegerFourBytes instanceId) throws AVTransportException {
285         return findStateMachine(instanceId, true);
286     }
287 
288     protected AVTransportStateMachine findStateMachine(UnsignedIntegerFourBytes instanceId, boolean createDefaultTransport) throws AVTransportException {
289         synchronized (stateMachines) {
290             long id = instanceId.getValue();
291             AVTransportStateMachine stateMachine = stateMachines.get(id);
292             if (stateMachine == null && id == 0 && createDefaultTransport) {
293                 log.fine("Creating default transport instance with ID '0'");
294                 stateMachine = createStateMachine(instanceId);
295                 stateMachines.put(id, stateMachine);
296             } else if (stateMachine == null) {
297                 throw new AVTransportException(AVTransportErrorCode.INVALID_INSTANCE_ID);
298             }
299             log.fine("Found transport control with ID '" + id + "'");
300             return stateMachine;
301         }
302     }
303 
304     protected AVTransportStateMachine createStateMachine(UnsignedIntegerFourBytes instanceId) {
305         // Create a proxy that delegates all calls to the right state implementation, working on the T state
306         return StateMachineBuilder.build(
307                 stateMachineDefinition,
308                 initialState,
309                 new Class[]{transportClass},
310                 new Object[]{createTransport(instanceId, getLastChange())}
311         );
312     }
313 
314     protected AVTransport createTransport(UnsignedIntegerFourBytes instanceId, LastChange lastChange) {
315         return new AVTransport(instanceId, lastChange, StorageMedium.NETWORK);
316     }
317 
318 }