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