水の確保が難しい地域では、海水を飲料水に変える「淡水化装置」が重宝されます。特に、どこでも利用できる携帯型の淡水化装置は、離島や船で大いに役立つでしょう。
しかし、従来の装置は何度もフィルターを交換しなければならず、不便でした。
そこでアメリカ・マサチューセッツ工科大学(MIT)の電子工学者ジョンヨン・ハン氏ら研究チームは、フィルターいらずの携帯型淡水化装置を開発しました。
⇧ amazing...漂流しても飲み水に困らないってのは革命的ですね。
個人利用だとMicrosoftアカウントかGitHubアカウントが必須
公式のドキュメントだと見当たらないのですが、
Azureアカウントを作成するために必要なものは、以下の3点です。
●クレジットカード
●利用可能な電話番号
●Microsoftアカウント、もしくはGitHubアカウント
Microsoftアカウントは、WindowsやOffice 365などのMicrosoft製品/サービスを利用するために必要な共通アカウントです。そのため、すでに持っている方も多いかもしれません。 アカウントの情報を確認することができます。
⇧ 上記サイト様が「Azure アカウント」作成するにあたり必要な情報をまとめてくださってます。
まずは、Microsoft Azureのアカウント作成で
何はともあれ、Microsoft Azureのアカウントを作成しないと、Azureのサービスは一切利用できないので、作成します。
作成して30日間だけ無料期間なので、始めるタイミングに迷うところですよね...
そもそも自分の場合、
⇧ 過去に「Azure アカウント」を一度作成していて、
⇧ アップグレードしなかった記憶が...
⇧う~ん、過去に「Azure アカウント」を一度作成していると、無料期間は付いてこない感じですかね...
新たに作成できるのか試してみることに。
このあたりは、自分は一度作成してるということで、電話番号とか設定されてましたが、各々の情報を入力すればOK。「テキスト メッセージを送信する」ボタンを押下。
スマホに届いた「認証コード」を入力して、「コードの確認」ボタンを押下。
クレジットカードが有効期限切れになってる場合は、「新しい支払方法を追加する」を選択。
クレジットカードが更新されてる場合は、無料アカウント作成で特典が付いてくるみたい。
メールでも、無料アカウントの特典が付与された旨が記載されてました、一安心。
Azure Media Servicesのアカウント作成で
とりあえず、Azure Media Servicesを探します。
「メディア サービスの作成」ボタンを押下。
「リソース グループ」は「新規作成」で適当に作成してます。「Media Services アカウント名」を適当に入力し、「新しいストレージ アカウントの作成」のリンクを選択。
「タグ」については、今回は割愛しますが、
⇧ 上記が詳しいです。
画面下部の同意にチェックをしてから、「確認および作成」タブを選択。
「作成」ボタンを押下。
「すべてのリソース」を確認すると、
⇧ 作成した「Azure リソース」が確認できます。
依存関係は旧いものは非推奨らしい
出だしから躓いてますが、
⇧ なんか、公式には載ってないのが、Maven Repositoryにはあるというところが引っかかって調べたところ、
Historical Releases
Note that the latest libraries from Microsoft are in the com.azure
Maven group ID, and have the package naming pattern of beginning with com.azure
. If you're using libraries that are in com.microsoft.azure
Maven group ID, or have this as the package structure, please consider migrating to the latest libraries. You can find a mapping table from these historical releases to their equivalent here.
⇧ どうやら、依存関係は、
- com.azure
- com.microsoft.azure
の2つあるらしく、「com.azure」のほうが新しいほうだから、新しいほうを使ってください、ということらしい...。
公式のサンプルが公開されてるので、
⇧ 各サンプルプロジェクトとのpom.xmlを参考に「依存関係」を追加していきます。
Azure Media ServicesのAPIの利用に、Azure Active Directory (Azure AD) のIDが必須な件
で、早速、必要な情報が確認できないのだが...
なんか、CLIがあるみたいなので、
⇧ インストールしてみたのですが、
ドキュメントを参考に、
コマンドで確認しようとしたら、
まさかの正常に動かないという...流石はMicrosoftさん。
動くコマンドもあるにはあるけど、
Azure Portalだと、「Azure テナント ID」が設定されてるんだけど、コマンドだとnullって...ちょっと酷すぎるんじゃないですかね、Microsoftさん...。
で、最終的に、調べた限りでは、
⇧ 上記サイト様によりますと、「AAD(Azure Active Directory)アプリID」なるものを作成する必要があるみたい。
「サービス プリンシパルの認証」タブを選択した状態で、
「ADD アプリとシークレットの管理」で「ADD アプリ」の「新規作成」のリンクを押下。
続いて、「シークレット」で「新規作成」のリンクを押下。
「説明」を適当に入力し、今回「有効期限」は「1年」にしてますが、一般的には「なし」とかにするのではないかと。
作成が済むと、
「Azure クライアント ID」「Azure クライアント シークレット」が自動で設定されるようです。ただ、ページの再読み込みとかすると、設定がリセットされるので、適当なエディターなりにコピっときましょう。
2022年5月18日(水)追記:↓ ここから
どうやら、Azureのリソースとして表示されないのですが、「Azure Active Directory」を選択し、
「App registrations」を選択し、
「All applications」タブを選択し、「Display name」に表示されてるリンクを選択すると、
「Azure クライアント ID」については「Application (client) ID」の値で確認できました。ちなみに、「Client credentials」を確認してみると、
「Secret ID」なるものは確認できるのですが、「Azure クライアント シークレット」の値は確認できず...
「Value」が「Azure クライアント シークレット」の値らしいのですが、
Client secret values cannot be viewed, except for immediately after creation. Be sure to save the secret when created before leaving the page.
⇧ とメッセージが表示されていて、Google翻訳したところ
『クライアントシークレット値は、作成直後を除いて表示できません。ページを離れる前に、作成時にシークレットを必ず保存してください。』
とあるんですが、この仕様はどうにかしたほうが良いような...
2022年5月18日(水)追記:↑ ここまで
2022年5月31日(火)追記:↓ ここから
なんか、
⇧ 上記の説明によると、
- Microsoft Azure Access Control Service (ACS)-based authentication
- Azure Active Directory (AAD) authentication
認証方法が、「ADD(Azure Active Directory)」を利用した方式に変わっていて、
Making it easy to get started with the new API Access Blade for Media Services
Azure Active Directory authentication could be complex for users unfamiliar with the details of AAD, so we wanted to make it very easy to get started with very little knowledge of AAD. For that reason, we are introducing a new "API Access" blade for Media Services accounts in the portal that will replace the previous ACS "Account keys" blade. We are also disabling the ability to rotate the ACS keys to promote users to update their code and move to AAD support.
https://azure.microsoft.com/ja-jp/blog/azure-media-service-aad-auth-and-acs-deprecation/
⇧「ADD(Azure Active Directory)」の認証はユーザーにとって「complex」だから「API access」を用意しましたってことらしい。
以上のことからソフトウェアの設計において、ComplexとComplicatedを次のように使いたい。
Complex: 1つのエンティティ、1つのクラス、1つのfunctionに複数の責務・概念が混ざっている。単一責任原則(SRP)に違反した状態
Complicated: エンティティやクラス、functionの数が多く、それぞれの役割や関係性を理解するのが厄介。
⇧ う~む、「ADD(Azure Active Directory)」は、構造的にイケてないってことなんかな?
話が脱線しましたが、
It depends on whether your application supports personal account login. If your application supports personal account login, you can only create two client secrets at most.
If your application only supports work account login, there will be no limit to the number of client secrets created.
You can learn about or modify the account types supported by the application by viewing the manifest of the application. It is the signInAudience attribute in the manifest.
⇧ 衝撃...secretが2つまでしか作成できないとかアカンやろ...
signInAudience attribute
Specifies what Microsoft accounts are supported for the current application. Supported values are:
AzureADMyOrg
- Users with a Microsoft work or school account in my organization's Azure AD tenant (for example, single tenant)AzureADMultipleOrgs
- Users with a Microsoft work or school account in any organization's Azure AD tenant (for example, multi-tenant)AzureADandPersonalMicrosoftAccount
- Users with a personal Microsoft account, or a work or school account in any organization's Azure AD tenantPersonalMicrosoftAccount
- Personal accounts that are used to sign in to services like Xbox and Skype.
⇧「PersonalMicrosoftAccount」じゃなければ制限ないってことなんかな?
確認してみた。
⇧ secretの作成できる数に制限はないってことで良いんかな?
2022年5月31日(火)追記:↑ ここまで
Azure Media Servicesのストリーミングエンドポイントは動画配信しなくても作成が必要な件
動画配信しないのであれば、「ストリーミングエンドポイント」は作成不要と思いきや、然に非ず。
手前味噌になり恐縮ですが、
⇧ 上記でまとめさせていただいております。
どうも、「VoD(Video on Demand)」での動画配信の場合は、「動画」を「エンコード」して、「Asset」の作成までであっても、「ストリーミングエンドポイント」が必要ってことになるらしい...少なくとも、公式のJavaのサンプルだと必要みたい。
なので、作成。
CDNはなしにしました。
Azure Media ServicesのSDK(Software Development Kit)のJavaを試してみる
実際に動くか試してみました。
コードについては、
⇧ 上記の時のものを流用してます。
新規追加、修正を加えた部分だけ記載。
◇フロントエンド
■C:\Users\Toshinobu\Desktop\soft_work\vue_work\vue-router-work\my-project-vue\src\views\AboutView.vue
<template> <div class="about"> <h1>This is an about page</h1> <label >画像を選択 <input type="file" @change="onFileChange" /> </label> <button @click="removeFile">削除</button> <button @click="uploadFile">アップロード</button> <div> <video v-if="isMovie" :src="selectedFile" /> <img v-else :src="selectedFile" /> </div> <!--<img :src="require('@/assets/images/SVGアイコン.svg')" />--> </div> </template> <script lang="ts"> /* eslint-disable no-console */ import { Component, Vue, Watch } from "vue-property-decorator"; import UserService from "../api/userService"; import UserDto from "../dto/user/UserDto"; @Component export default class AboutView extends Vue { private temp = ""; private selectedFile = ""; private tempFile = {}; private userDto?: any; private isMovie = false; created() { //this.test(); } private onFileChange(event: any) { const files = event.target.files || event.dataTransfer.files; this.tempFile = files[0]; this.createDisplayImage(files[0], event); } private createDisplayImage(file: any, event: any) { const fileReader = new FileReader(); fileReader.onload = (event) => { console.log("createDisplayImage"); console.dir(event); if (event && event.target) { this.selectedFile = event.target.result as string; this.isMovie = this.isMimeTypeOfMovie(file.type); } }; fileReader.readAsDataURL(file); } private removeFile() { this.selectedFile = ""; } private uploadFile() { console.log("■created()"); console.dir(this.selectedFile); console.dir(this.tempFile); let data = "テスト"; this.userDto = Object.entries({ id: data, file: this.tempFile as Blob, }); let formData = new FormData(); formData.append("id", data); formData.append("file", this.tempFile as File); //formData.append("userFormDto", JSON.stringify(userDto as any)); formData.append("userFormDto", this.userDto); var postdata = new URLSearchParams(); postdata.append("userFormDto", this.userDto); //postdata.append("id", data); //postdata.append("file", JSON.stringify(this.tempFile as File)); // https://tech.chakapoko.com/nodejs/http/axios.html console.log("■送信前"); UserService.save(formData) //UserService.save(postdata) .then((result) => { console.log("■レスポンス受信"); console.log("■バックエンドからの返却の値"); console.dir(result); }) .catch((error) => { console.log(error); }); console.log("■処理中..."); console.dir(UserService.chunkedArr); //const numArr = [...Array(100000).keys()].map((i: number) => ++i); const numArr = Array.from(Array(1000000).keys()).map((x: number) => x + 1); let response = "["; for (let val of numArr) { let tmp = val === numArr.length ? "" : ","; response = response.concat(val.toString(), tmp); } response += "]"; console.log("■フロントエンド"); console.log(response); } private test() { console.log("■created()"); let data = "テスト"; let formData = new FormData(); formData.append("id", data); // https://tech.chakapoko.com/nodejs/http/axios.html console.log("■送信前"); UserService.save(formData) .then((result) => { console.log("■レスポンス受信"); console.log("■バックエンドからの返却の値"); console.dir(result); }) .catch((error) => { console.log(error); }); console.log("■処理中..."); console.dir(UserService.chunkedArr); //const numArr = [...Array(100000).keys()].map((i: number) => ++i); const numArr = Array.from(Array(1000000).keys()).map((x: number) => x + 1); let response = "["; for (let val of numArr) { let tmp = val === numArr.length ? "" : ","; response = response.concat(val.toString(), tmp); } response += "]"; console.log("■フロントエンド"); console.log(response); } @Watch("temp") private setInfo() { console.dir(this.temp); } private isMimeTypeOfMovie(mimeType: string): boolean { if (mimeType) { switch (mimeType) { case "video/mp4": case "video/mov": return true; default: return false; } } return false; } } </script>
続いて、サーバーサイド。新規で追加したファイルは、以下の2ファイル。
◇サーバーサイド
■test-uml/build.gradle
plugins { id 'org.springframework.boot' version '2.6.6' id 'io.spring.dependency-management' version '1.0.11.RELEASE' id 'java' } group = 'com.example' version = '0.0.1-SNAPSHOT' sourceCompatibility = '17' configurations { compileOnly { extendsFrom annotationProcessor } } repositories { mavenCentral() } dependencies { implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' implementation 'org.springframework.boot:spring-boot-starter-web' compileOnly 'org.projectlombok:lombok' developmentOnly 'org.springframework.boot:spring-boot-devtools' runtimeOnly 'org.postgresql:postgresql' annotationProcessor 'org.projectlombok:lombok' testImplementation 'org.springframework.boot:spring-boot-starter-test' // https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-actuator implementation 'org.springframework.boot:spring-boot-starter-actuator' implementation 'com.azure.resourcemanager:azure-resourcemanager-mediaservices:2.0.0' implementation 'com.googlecode.json-simple:json-simple:1.1.1' implementation 'com.azure:azure-storage-blob:12.16.0' implementation 'com.azure:azure-identity:1.5.0' } tasks.named('test') { useJUnitPlatform() }
■test-uml/src/main/resources/application.properties
# application server port server.port=8088 # cors management.endpoints.web.cors.allowed-origins=http://localhost:8080 # database spring.datasource.driver-class-name=org.postgresql.Driver spring.datasource.url=jdbc:postgresql://localhost:5434/test spring.datasource.username=postgres spring.datasource.password=postgres # multipart spring.servlet.multipart.enabled=true spring.servlet.multipart.max-file-size=1024MB spring.servlet.multipart.max-request-size=1025MB # Azure Media Services azure.media.client.id=[Azure クライアント IDの値] azure.media.client.secret=[Azure クライアント シークレットの値] azure.media.tenant.id=[Azure テナント IDの値] azure.media.service.account.name=[Azure Media Services アカウント名の値] azure.media.arm.token.id=[Azure ARM トークン対象ユーザーの値] azure.media.arm.endpoint=[Azure ARM エンドポイントの値] azure.media.region=[リージョンの値] azure.resource.group=[Azure Resource グループの値] azure.subscription.id=[Azure サブスクリプション IDの値]
⇧ Azure Portalの「Azure Media Services」でそれぞれの値を確認して設定します。
ちなみに、Eclipseだと、application.propertiesでカスタムなプロパティを利用すると警告が出ますが、プロパティの値として利用はできます。
■test-uml/src/main/java/com/example/demo/service/azure/media/ConfigWrapper.java
// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. package com.example.demo.service.azure.media; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Configuration; import lombok.Data; import lombok.NoArgsConstructor; /** * This class reads values from local configuration file * resources/conf/appsettings.json. * Please change the configuration using your account information. For more * information, see * https://docs.microsoft.com/azure/media-services/latest/access-api-cli-how-to. * For security * reasons, do not check in the configuration file to source control. */ @Configuration //@AllArgsConstructor @NoArgsConstructor @Data public class ConfigWrapper { @Value("${azure.media.client.id}") private String azureClientId; @Value("${azure.media.client.secret}") private String azureClientSecret; @Value("${azure.media.tenant.id}") private String aadTenantId; @Value("${azure.media.service.account.name}") private String azureMediaServicesAccountName; @Value("${azure.media.arm.token.id}") private String azureArmTokenAudience; @Value("${azure.media.arm.endpoint}") private String azureArmEndpoint; @Value("${azure.media.region}") private String region; @Value("${azure.resource.group}") private String azureResourceGroup; @Value("${azure.subscription.id}") private String azureSubscriptionId; // private static String CONF_JSON = "conf/appsettings.json"; // private final JSONObject jsonObject; // private final InputStreamReader isReader; // public ConfigWrapper() { // InputStream inStream = ConfigWrapper.class.getClassLoader().getResourceAsStream(CONF_JSON); // isReader = new InputStreamReader(inStream); // // JSONParser parser = new JSONParser(); // Object obj = null; // try { // obj = parser.parse(isReader); // } catch (Exception ioe) { // System.err.println(ioe); // System.exit(1); // } // // jsonObject = (JSONObject) obj; // } // // public void close() { // try { // if (isReader != null) { // isReader.close(); // } // } catch (IOException e) { // e.printStackTrace(); // } // } // // public String getAadClientId() { // return (String) jsonObject.get(AZURE_CLIENT_ID); // } // // public String getAadSecret() { // return (String) jsonObject.get(AZURE_CLIENT_SECRET); // } // // public String getAadTenantId() { // return (String) jsonObject.get(AAD_TENANT_ID); // } // // public String getAccountName() { // return (String) jsonObject.get(AZURE_MEDIA_SERVICES_ACCOUNT_NAME); // } // // public String getArmAadAudience() { // return (String) jsonObject.get(AZURE_ARM_TOKEN_AUDIENCE); // } // // public String getArmEndpoint() { // return (String) jsonObject.get(AZURE_ARM_ENDPOINT); // } // // public String getRegion() { // return (String) jsonObject.get(REGION); // } // // public String getResourceGroup() { // return (String) jsonObject.get(AZURE_RESOURCE_GROUP); // } // // public String getSubscriptionId() { // return (String) jsonObject.get(AZURE_SUBSCRIPTION_ID); // } }
■test-uml/src/main/java/com/example/demo/service/azure/media/EncodingWithMESPredefinedPreset.java
// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. package com.example.demo.service.azure.media; import java.io.File; import java.io.IOException; import java.net.URISyntaxException; import java.time.OffsetDateTime; import java.util.ArrayList; import java.util.List; import java.util.UUID; import javax.naming.AuthenticationException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; import com.azure.core.credential.TokenCredential; import com.azure.core.http.policy.HttpLogDetailLevel; import com.azure.core.http.policy.HttpLogOptions; import com.azure.core.management.exception.ManagementException; import com.azure.core.management.profile.AzureProfile; import com.azure.identity.ClientSecretCredentialBuilder; import com.azure.resourcemanager.mediaservices.MediaServicesManager; import com.azure.resourcemanager.mediaservices.models.Asset; import com.azure.resourcemanager.mediaservices.models.AssetContainerPermission; import com.azure.resourcemanager.mediaservices.models.AssetContainerSas; import com.azure.resourcemanager.mediaservices.models.BuiltInStandardEncoderPreset; import com.azure.resourcemanager.mediaservices.models.EncoderNamedPreset; import com.azure.resourcemanager.mediaservices.models.Job; import com.azure.resourcemanager.mediaservices.models.JobInput; import com.azure.resourcemanager.mediaservices.models.JobInputAsset; import com.azure.resourcemanager.mediaservices.models.JobOutput; import com.azure.resourcemanager.mediaservices.models.JobOutputAsset; import com.azure.resourcemanager.mediaservices.models.JobState; import com.azure.resourcemanager.mediaservices.models.ListContainerSasInput; import com.azure.resourcemanager.mediaservices.models.ListPathsResponse; import com.azure.resourcemanager.mediaservices.models.StreamingEndpoint; import com.azure.resourcemanager.mediaservices.models.StreamingEndpointResourceState; import com.azure.resourcemanager.mediaservices.models.StreamingLocator; import com.azure.resourcemanager.mediaservices.models.StreamingPath; import com.azure.resourcemanager.mediaservices.models.Transform; import com.azure.resourcemanager.mediaservices.models.TransformOutput; import com.azure.storage.blob.BlobClient; import com.azure.storage.blob.BlobContainerClient; import com.azure.storage.blob.BlobContainerClientBuilder; @Service public class EncodingWithMESPredefinedPreset { @Autowired private ConfigWrapper configWrapper; private static final String TRANSFORM_NAME = "AdaptiveBitrate"; private static final String OUTPUT_FOLDER = "Output"; //private static final String BASE_URI = "https://nimbuscdn-nimbuspm.streaming.mediaservices.windows.net/2b533311-b215-4409-80af-529c3e853622/"; //private static final String INPUT_LABEL = "input1"; // Please change this to your endpoint name private static final String STREAMING_ENDPOINT_NAME = "default"; private MediaServicesManager createMediaServicesManager () { // Connect to media services, please see https://docs.microsoft.com/en-us/azure/media-services/latest/configure-connect-java-howto // for details. TokenCredential credential = new ClientSecretCredentialBuilder() .clientId(configWrapper.getAzureClientId()) .clientSecret(configWrapper.getAzureClientSecret()) .tenantId(configWrapper.getAadTenantId()) .build(); AzureProfile profile = new AzureProfile(configWrapper.getAadTenantId(), configWrapper.getAzureSubscriptionId(), com.azure.core.management.AzureEnvironment.AZURE); // MediaServiceManager is the entry point to Azure Media resource management. MediaServicesManager manager = MediaServicesManager.configure() .withLogOptions(new HttpLogOptions().setLogLevel(HttpLogDetailLevel.BODY_AND_HEADERS)) .authenticate(credential, profile); return manager; } /** * Run the sample. * * @param config This param is of type ConfigWrapper. This class reads values from local configuration file. */ public void runEncodingWithMESPredefinedPreset(MultipartFile uploadFile) { // MediaServiceManager is the entry point to Azure Media resource management. MediaServicesManager manager = createMediaServicesManager(); // Creating a unique suffix so that we don't have name collisions if you run the // sample UUID uuid = UUID.randomUUID(); String uniqueness = uuid.toString(); String jobName = "job-" + uniqueness.substring(0, 13); String locatorName = "locator-" + uniqueness; String outputAssetName = "output-" + uniqueness; String inputAssetName = "input-" + uniqueness; boolean stopEndpoint = false; try { List<TransformOutput> outputs = new ArrayList<>(); outputs.add(new TransformOutput().withPreset( new BuiltInStandardEncoderPreset().withPresetName(EncoderNamedPreset.CONTENT_AWARE_ENCODING))); // Create the transform. System.out.println("Creating a transform..."); Transform transform = manager.transforms() .define(TRANSFORM_NAME) .withExistingMediaService(configWrapper.getAzureResourceGroup(), configWrapper.getAzureMediaServicesAccountName()) .withOutputs(outputs) .create(); System.out.println("Transform created"); // Create a JobInputHttp. The input to the Job is a HTTPS URL pointing to an MP4 file. // List<String> files = new ArrayList<>(); // files.add(uploadFile.getOriginalFilename()); // JobInputHttp input = new JobInputHttp().withBaseUri(BASE_URI); // input.withFiles(files); // input.withLabel(INPUT_LABEL); Asset inputAsset = createInputAsset(manager, configWrapper.getAzureResourceGroup(), configWrapper.getAzureMediaServicesAccountName(), inputAssetName, uploadFile); // Output from the encoding Job must be written to an Asset, so let's create one. Note that we // are using a unique asset name, there should not be a name collision. System.out.println("Creating an output asset..."); Asset outputAsset = manager.assets() .define(outputAssetName) .withExistingMediaService(configWrapper.getAzureResourceGroup(), configWrapper.getAzureMediaServicesAccountName()) .create(); Job job = submitJob(manager, configWrapper.getAzureResourceGroup(), configWrapper.getAzureMediaServicesAccountName(), transform.name(), jobName, inputAsset.name(), outputAsset.name()); long startedTime = System.currentTimeMillis(); // In this demo code, we will poll for Job status. Polling is not a recommended best practice for production // applications because of the latency it introduces. Overuse of this API may trigger throttling. Developers // should instead use Event Grid. To see how to implement the event grid, see the sample // https://github.com/Azure-Samples/media-services-v3-java/tree/master/ContentProtection/BasicAESClearKey. job = waitForJobToFinish(manager, configWrapper.getAzureResourceGroup(), configWrapper.getAzureMediaServicesAccountName(), transform.name(), jobName); long elapsed = (System.currentTimeMillis() - startedTime) / 1000; // Elapsed time in seconds System.out.println("Job elapsed time: " + elapsed + " second(s)."); if (job.state() == JobState.FINISHED) { System.out.println("Job finished."); System.out.println(); // Now that the content has been encoded, publish it for Streaming by creating // a StreamingLocator. StreamingLocator locator = getStreamingLocator(manager, configWrapper.getAzureResourceGroup(), configWrapper.getAzureMediaServicesAccountName(), outputAsset.name(), locatorName); StreamingEndpoint streamingEndpoint = manager.streamingEndpoints() .get(configWrapper.getAzureResourceGroup(), configWrapper.getAzureMediaServicesAccountName(), STREAMING_ENDPOINT_NAME); if (streamingEndpoint != null) { // Start The Streaming Endpoint if it is not running. if (streamingEndpoint.resourceState() != StreamingEndpointResourceState.RUNNING) { System.out.println("Streaming endpoint was stopped, restarting it..."); manager.streamingEndpoints().start(configWrapper.getAzureResourceGroup(), configWrapper.getAzureMediaServicesAccountName(), STREAMING_ENDPOINT_NAME); // We started the endpoint, we should stop it in cleanup. stopEndpoint = true; } System.out.println(); System.out.println("Streaming urls:"); List<String> urls = getStreamingUrls(manager, configWrapper.getAzureResourceGroup(), configWrapper.getAzureMediaServicesAccountName(), locator.name(), streamingEndpoint); for (String url : urls) { System.out.println("\t" + url); } System.out.println(); System.out.println("To stream, copy and paste the Streaming URL into the Azure Media Player at 'http://aka.ms/azuremediaplayer'."); System.out.println("When finished, press ENTER to continue."); System.out.println(); System.out.flush(); // Download output asset for verification. System.out.println("Downloading output asset..."); System.out.println(); File outputFolder = new File(OUTPUT_FOLDER); if (outputFolder.exists() && !outputFolder.isDirectory()) { outputFolder = new File(OUTPUT_FOLDER + uniqueness); } if (!outputFolder.exists()) { outputFolder.mkdir(); } downloadResults(manager, configWrapper.getAzureResourceGroup(), configWrapper.getAzureMediaServicesAccountName(), outputAsset.name(), outputFolder); System.out.println("Done downloading. Please check the files at " + outputFolder.getAbsolutePath()); } else { System.out.println("Could not find streaming endpoint: " + STREAMING_ENDPOINT_NAME); } System.out.println("When finished, press ENTER to cleanup."); System.out.println(); System.out.flush(); } else if (job.state() == JobState.ERROR) { System.out.println("ERROR: Job finished with error message: " + job.outputs().get(0).error().message()); System.out.println("ERROR: error details: " + job.outputs().get(0).error().details().get(0).message()); } } catch (Exception e) { Throwable cause = e; while (cause != null) { if (cause instanceof AuthenticationException) { System.out.println("ERROR: Authentication error, please check your account settings in appsettings.json."); break; } else if (cause instanceof ManagementException) { ManagementException apiException = (ManagementException) cause; System.out.println("ERROR: " + apiException.getValue().getMessage()); break; } cause = cause.getCause(); } System.out.println(); e.printStackTrace(); System.out.println(); } finally { System.out.println("Cleaning up..."); cleanup(manager, configWrapper.getAzureResourceGroup(), configWrapper.getAzureMediaServicesAccountName(), TRANSFORM_NAME, jobName, outputAssetName, locatorName, stopEndpoint, STREAMING_ENDPOINT_NAME); System.out.println("Done."); } } /** * Create and submit a job. * * @param manager The entry point of Azure Media resource management. * @param resourceGroup The name of the resource group within the Azure subscription. * @param accountName The Media Services account name. * @param transformName The name of the transform. * @param jobName The name of the job. * @param jobInput The input to the job. * @param outputAssetName The name of the asset that the job writes to. * @return The job created. */ private static Job submitJob(MediaServicesManager manager, String resourceGroup, String accountName, String transformName, String jobName, String inputAssetName, String outputAssetName) { System.out.println("Creating a job..."); // Use the name of the created input asset to create the job input. JobInput jobInput = new JobInputAsset().withAssetName(inputAssetName); // First specify where the output(s) of the Job need to be written to List<JobOutput> jobOutputs = new ArrayList<>(); jobOutputs.add(new JobOutputAsset().withAssetName(outputAssetName)); Job job = manager.jobs().define(jobName) .withExistingTransform(resourceGroup, accountName, transformName) .withInput(jobInput) .withOutputs(jobOutputs) .create(); return job; } /** * Polls Media Services for the status of the Job. * * @param manager This is the entry point of Azure Media resource * management * @param resourceGroup The name of the resource group within the Azure * subscription * @param accountName The Media Services account name * @param transformName The name of the transform * @param jobName The name of the job submitted * @return The job */ private static Job waitForJobToFinish(MediaServicesManager manager, String resourceGroup, String accountName, String transformName, String jobName) { final int SLEEP_INTERVAL = 10 * 1000; Job job = null; boolean exit = false; do { job = manager.jobs().get(resourceGroup, accountName, transformName, jobName); if (job.state() == JobState.FINISHED || job.state() == JobState.ERROR || job.state() == JobState.CANCELED) { exit = true; } else { System.out.println("Job is " + job.state()); int i = 0; for (JobOutput output : job.outputs()) { System.out.print("\tJobOutput[" + i++ + "] is " + output.state() + "."); if (output.state() == JobState.PROCESSING) { System.out.print(" Progress: " + output.progress()); } System.out.println(); } try { Thread.sleep(SLEEP_INTERVAL); } catch (InterruptedException e) { e.printStackTrace(); } } } while (!exit); return job; } /** * Use Media Service and Storage APIs to download the output files to a local folder * * @param manager The entry point of Azure Media resource management * @param resourceGroup The name of the resource group within the Azure subscription * @param accountName The Media Services account name * @param assetName The asset name * @param outputFolder The output folder for downloaded files. * @throws Exception * @throws URISyntaxException * @throws IOException */ private static void downloadResults(MediaServicesManager manager, String resourceGroup, String accountName, String assetName, File outputFolder) throws URISyntaxException, IOException { ListContainerSasInput parameters = new ListContainerSasInput() .withPermissions(AssetContainerPermission.READ) .withExpiryTime(OffsetDateTime.now().plusHours(1)); AssetContainerSas assetContainerSas = manager.assets() .listContainerSas(resourceGroup, accountName, assetName, parameters); BlobContainerClient container = new BlobContainerClientBuilder() .endpoint(assetContainerSas.assetContainerSasUrls().get(0)) .buildClient(); File directory = new File(outputFolder, assetName); directory.mkdir(); container.listBlobs().forEach(blobItem -> { BlobClient blob = container.getBlobClient(blobItem.getName()); File downloadTo = new File(directory, blobItem.getName()); blob.downloadToFile(downloadTo.getAbsolutePath()); }); System.out.println("Download complete."); } /** * Creates a StreamingLocator for the specified asset and with the specified streaming policy name. * Once the StreamingLocator is created the output asset is available to clients for playback. * * @param manager The entry point of Azure Media resource management * @param resourceGroup The name of the resource group within the Azure subscription * @param accountName The Media Services account name * @param assetName The name of the output asset * @param locatorName The StreamingLocator name (unique in this case) * @return The locator created */ private static StreamingLocator getStreamingLocator(MediaServicesManager manager, String resourceGroup, String accountName, String assetName, String locatorName) { // Note that we are using one of the PredefinedStreamingPolicies which tell the Origin component // of Azure Media Services how to publish the content for streaming. System.out.println("Creating a streaming locator..."); StreamingLocator locator = manager .streamingLocators().define(locatorName) .withExistingMediaService(resourceGroup, accountName) .withAssetName(assetName) .withStreamingPolicyName("Predefined_ClearStreamingOnly") .create(); return locator; } /** * Checks if the streaming endpoint is in the running state, if not, starts it. * * @param manager The entry point of Azure Media resource management * @param resourceGroup The name of the resource group within the Azure subscription * @param accountName The Media Services account name * @param locatorName The name of the StreamingLocator that was created * @param streamingEndpoint The streaming endpoint. * @return List of streaming urls */ private static List<String> getStreamingUrls(MediaServicesManager manager, String resourceGroup, String accountName, String locatorName, StreamingEndpoint streamingEndpoint) { List<String> streamingUrls = new ArrayList<>(); ListPathsResponse paths = manager.streamingLocators().listPaths(resourceGroup, accountName, locatorName); for (StreamingPath path : paths.streamingPaths()) { StringBuilder uriBuilder = new StringBuilder(); uriBuilder.append("https://") .append(streamingEndpoint.hostname()) .append("/") .append(path.paths().get(0)); streamingUrls.add(uriBuilder.toString()); } return streamingUrls; } /** * Cleanup * * @param manager The entry point of Azure Media resource management. * @param resourceGroupName The name of the resource group within the Azure subscription. * @param accountName The Media Services account name. * @param transformName The transform name. * @param jobName The job name. * @param assetName The asset name. * @param streamingLocatorName The streaming locator name. * @param stopEndpoint Stop endpoint if true, otherwise keep endpoint running. * @param streamingEndpointName The endpoint name. */ private static void cleanup(MediaServicesManager manager, String resourceGroupName, String accountName, String transformName, String jobName, String assetName, String streamingLocatorName, boolean stopEndpoint, String streamingEndpointName) { if (manager == null) { return; } manager.jobs().delete(resourceGroupName, accountName, transformName, jobName); manager.assets().delete(resourceGroupName, accountName, assetName); manager.streamingLocators().delete(resourceGroupName, accountName, streamingLocatorName); if (stopEndpoint) { // Because we started the endpoint, we'll stop it. manager.streamingEndpoints().stop(resourceGroupName, accountName, streamingEndpointName); } else { // We will keep the endpoint running because it was not started by this sample. Please note, There are costs to keep it running. // Please refer https://azure.microsoft.com/en-us/pricing/details/media-services/ for pricing. System.out.println("The endpoint '" + streamingEndpointName + "' is running. To halt further billing on the endpoint, please stop it in azure portal or AMS Explorer."); } } /** * Creates a new input Asset and uploads the specified local video file into it. * * @param manager This is the entry point of Azure Media resource * management. * @param resourceGroupName The name of the resource group within the Azure * subscription. * @param accountName The Media Services account name. * @param assetName The name of the asset where the media file to * uploaded to. * @param mediaFile The path of a media file to be uploaded into the * asset. * @return The asset. */ private static Asset createInputAsset(MediaServicesManager manager, String resourceGroupName, String accountName, String assetName, MultipartFile mediaFile) throws Exception { System.out.println("Creating an input asset..."); // Call Media Services API to create an Asset. // This method creates a container in storage for the Asset. // The files (blobs) associated with the asset will be stored in this container. Asset asset = manager.assets().define(assetName).withExistingMediaService(resourceGroupName, accountName) .create(); // Use Media Services API to get back a response that contains // SAS URL for the Asset container into which to upload blobs. // That is where you would specify read-write permissions // and the expiration time for the SAS URL. ListContainerSasInput parameters = new ListContainerSasInput() .withPermissions(AssetContainerPermission.READ_WRITE).withExpiryTime(OffsetDateTime.now().plusHours(4)); AssetContainerSas response = manager.assets() .listContainerSas(resourceGroupName, accountName, assetName, parameters); // Use Storage API to get a reference to the Asset container // that was created by calling Asset's create method. BlobContainerClient container = new BlobContainerClientBuilder() .endpoint(response.assetContainerSasUrls().get(0)) .buildClient(); // Uploading from a local file: // URI fileToUpload = StreamHLSAndDASH.class.getClassLoader().getResource(mediaFile).toURI(); // The file is a // resource in // CLASSPATH. //File file = new File(fileToUpload); BlobClient blob = container.getBlobClient(mediaFile.getOriginalFilename()); // Use Storage API to upload the file into the container in storage. System.out.println("Uploading a media file to the asset..."); blob.upload(mediaFile.getInputStream(), mediaFile.getBytes().length); //blob.uploadFromFile(mediaFile.getOriginalFilename()); return asset; } }
■test-uml/src/main/java/com/example/demo/controller/UserController.java
package com.example.demo.controller; import java.io.IOException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; import com.example.demo.dto.user.UserFormDto; import com.example.demo.service.azure.media.EncodingWithMESPredefinedPreset; import lombok.extern.log4j.Log4j2; @Log4j2 @RestController @RequestMapping("/user") public class UserController { @Autowired private EncodingWithMESPredefinedPreset encodingWithMESPredefinedPreset; @PostMapping("/save") public SseEmitter save(@ModelAttribute @Validated UserFormDto userFormDto) throws IOException { log.info("■■■INPUT チェック"); log.info(userFormDto); log.info(userFormDto.getId()); log.info(userFormDto.getFile()); long timeOutForSseEmitter = 2 * 60 * 60 * 1000L; SseEmitter sseEmitter = new SseEmitter(timeOutForSseEmitter); // UserAsyncHelper userAsyncHelper = UserAsyncHelper.builder() // .sseEmitter(sseEmitter) // .userList(null) // .build(); // Thread th = new Thread(userAsyncHelper); log.info("■■■処理開始前"); sseEmitter.send("処理開始"); // th.start(); //userAsyncHelper.run(); log.info("■■■処理開始後"); encodingWithMESPredefinedPreset.runEncodingWithMESPredefinedPreset(userFormDto.getFile()); return sseEmitter; } }
⇧ 上記の変更で保存して、フロントエンド、サーバーサイドのアプリケーションのサーバーをそれぞれ起動して、
ブラウザで動画ファイルをアップロードします。
サーバーサイドで処理が行われ、
処理が正常に終了し、Azure Portalを確認すると、
ストリーミングの再生を確認するには、「ストリーミング ロケーター」も作成する必要がある模様。
⇧ 動画ファイルはアップロードできてるようです。「エンコード」の公式サンプルのソースコードを試してるので、「エンコード」もされてると信じたい。
2022年5月18日(水)追記:↓ ここから
手前味噌で恐縮ですが、
⇧ 上記の記事で確認したところ、test-uml/src/main/java/com/example/demo/service/azure/media/EncodingWithMESPredefinedPreset.javaのcleanupメソッドの中で、エンコード後の結果が含まれたAssetを削除してたからエンコード後のファイル群が残らなかったようなので、該当箇所をコメントアウトすれば、Azure Media Services にエンコード後の結果のAssetが保存され、紐づくAzure Blob StorageのBLOBコンテナーにファイル群が保存されるようです。
2022年5月18日(水)追記:↑ ここまで
とりあえずは、動画ファイルがアップロードできてるところまでは確認できましたかね。
課金が発生すかどうかが分からなかったので、一応、「ストリーミングエンドポイント」は停止しておきました。
今回はこのへんで。