1 /*
2  * Copyright (C) 2016 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package org.mozilla.thirdparty.com.google.android.exoplayer2.extractor.flv;
17 
18 import androidx.annotation.Nullable;
19 import org.mozilla.thirdparty.com.google.android.exoplayer2.C;
20 import org.mozilla.thirdparty.com.google.android.exoplayer2.ParserException;
21 import org.mozilla.thirdparty.com.google.android.exoplayer2.extractor.DummyTrackOutput;
22 import org.mozilla.thirdparty.com.google.android.exoplayer2.util.ParsableByteArray;
23 import java.util.ArrayList;
24 import java.util.Date;
25 import java.util.HashMap;
26 import java.util.Map;
27 
28 /**
29  * Parses Script Data tags from an FLV stream and extracts metadata information.
30  */
31 /* package */ final class ScriptTagPayloadReader extends TagPayloadReader {
32 
33   private static final String NAME_METADATA = "onMetaData";
34   private static final String KEY_DURATION = "duration";
35 
36   // AMF object types
37   private static final int AMF_TYPE_NUMBER = 0;
38   private static final int AMF_TYPE_BOOLEAN = 1;
39   private static final int AMF_TYPE_STRING = 2;
40   private static final int AMF_TYPE_OBJECT = 3;
41   private static final int AMF_TYPE_ECMA_ARRAY = 8;
42   private static final int AMF_TYPE_END_MARKER = 9;
43   private static final int AMF_TYPE_STRICT_ARRAY = 10;
44   private static final int AMF_TYPE_DATE = 11;
45 
46   private long durationUs;
47 
ScriptTagPayloadReader()48   public ScriptTagPayloadReader() {
49     super(new DummyTrackOutput());
50     durationUs = C.TIME_UNSET;
51   }
52 
getDurationUs()53   public long getDurationUs() {
54     return durationUs;
55   }
56 
57   @Override
seek()58   public void seek() {
59     // Do nothing.
60   }
61 
62   @Override
parseHeader(ParsableByteArray data)63   protected boolean parseHeader(ParsableByteArray data) {
64     return true;
65   }
66 
67   @Override
parsePayload(ParsableByteArray data, long timeUs)68   protected boolean parsePayload(ParsableByteArray data, long timeUs) throws ParserException {
69     int nameType = readAmfType(data);
70     if (nameType != AMF_TYPE_STRING) {
71       // Should never happen.
72       throw new ParserException();
73     }
74     String name = readAmfString(data);
75     if (!NAME_METADATA.equals(name)) {
76       // We're only interested in metadata.
77       return false;
78     }
79     int type = readAmfType(data);
80     if (type != AMF_TYPE_ECMA_ARRAY) {
81       // We're not interested in this metadata.
82       return false;
83     }
84     // Set the duration to the value contained in the metadata, if present.
85     Map<String, Object> metadata = readAmfEcmaArray(data);
86     if (metadata.containsKey(KEY_DURATION)) {
87       double durationSeconds = (double) metadata.get(KEY_DURATION);
88       if (durationSeconds > 0.0) {
89         durationUs = (long) (durationSeconds * C.MICROS_PER_SECOND);
90       }
91     }
92     return false;
93   }
94 
readAmfType(ParsableByteArray data)95   private static int readAmfType(ParsableByteArray data) {
96     return data.readUnsignedByte();
97   }
98 
99   /**
100    * Read a boolean from an AMF encoded buffer.
101    *
102    * @param data The buffer from which to read.
103    * @return The value read from the buffer.
104    */
readAmfBoolean(ParsableByteArray data)105   private static Boolean readAmfBoolean(ParsableByteArray data) {
106     return data.readUnsignedByte() == 1;
107   }
108 
109   /**
110    * Read a double number from an AMF encoded buffer.
111    *
112    * @param data The buffer from which to read.
113    * @return The value read from the buffer.
114    */
readAmfDouble(ParsableByteArray data)115   private static Double readAmfDouble(ParsableByteArray data) {
116     return Double.longBitsToDouble(data.readLong());
117   }
118 
119   /**
120    * Read a string from an AMF encoded buffer.
121    *
122    * @param data The buffer from which to read.
123    * @return The value read from the buffer.
124    */
readAmfString(ParsableByteArray data)125   private static String readAmfString(ParsableByteArray data) {
126     int size = data.readUnsignedShort();
127     int position = data.getPosition();
128     data.skipBytes(size);
129     return new String(data.data, position, size);
130   }
131 
132   /**
133    * Read an array from an AMF encoded buffer.
134    *
135    * @param data The buffer from which to read.
136    * @return The value read from the buffer.
137    */
readAmfStrictArray(ParsableByteArray data)138   private static ArrayList<Object> readAmfStrictArray(ParsableByteArray data) {
139     int count = data.readUnsignedIntToInt();
140     ArrayList<Object> list = new ArrayList<>(count);
141     for (int i = 0; i < count; i++) {
142       int type = readAmfType(data);
143       Object value = readAmfData(data, type);
144       if (value != null) {
145         list.add(value);
146       }
147     }
148     return list;
149   }
150 
151   /**
152    * Read an object from an AMF encoded buffer.
153    *
154    * @param data The buffer from which to read.
155    * @return The value read from the buffer.
156    */
readAmfObject(ParsableByteArray data)157   private static HashMap<String, Object> readAmfObject(ParsableByteArray data) {
158     HashMap<String, Object> array = new HashMap<>();
159     while (true) {
160       String key = readAmfString(data);
161       int type = readAmfType(data);
162       if (type == AMF_TYPE_END_MARKER) {
163         break;
164       }
165       Object value = readAmfData(data, type);
166       if (value != null) {
167         array.put(key, value);
168       }
169     }
170     return array;
171   }
172 
173   /**
174    * Read an ECMA array from an AMF encoded buffer.
175    *
176    * @param data The buffer from which to read.
177    * @return The value read from the buffer.
178    */
readAmfEcmaArray(ParsableByteArray data)179   private static HashMap<String, Object> readAmfEcmaArray(ParsableByteArray data) {
180     int count = data.readUnsignedIntToInt();
181     HashMap<String, Object> array = new HashMap<>(count);
182     for (int i = 0; i < count; i++) {
183       String key = readAmfString(data);
184       int type = readAmfType(data);
185       Object value = readAmfData(data, type);
186       if (value != null) {
187         array.put(key, value);
188       }
189     }
190     return array;
191   }
192 
193   /**
194    * Read a date from an AMF encoded buffer.
195    *
196    * @param data The buffer from which to read.
197    * @return The value read from the buffer.
198    */
readAmfDate(ParsableByteArray data)199   private static Date readAmfDate(ParsableByteArray data) {
200     Date date = new Date((long) readAmfDouble(data).doubleValue());
201     data.skipBytes(2); // Skip reserved bytes.
202     return date;
203   }
204 
205   @Nullable
readAmfData(ParsableByteArray data, int type)206   private static Object readAmfData(ParsableByteArray data, int type) {
207     switch (type) {
208       case AMF_TYPE_NUMBER:
209         return readAmfDouble(data);
210       case AMF_TYPE_BOOLEAN:
211         return readAmfBoolean(data);
212       case AMF_TYPE_STRING:
213         return readAmfString(data);
214       case AMF_TYPE_OBJECT:
215         return readAmfObject(data);
216       case AMF_TYPE_ECMA_ARRAY:
217         return readAmfEcmaArray(data);
218       case AMF_TYPE_STRICT_ARRAY:
219         return readAmfStrictArray(data);
220       case AMF_TYPE_DATE:
221         return readAmfDate(data);
222       default:
223         // We don't log a warning because there are types that we knowingly don't support.
224         return null;
225     }
226   }
227 }
228