Windows Media Player 10

Publicado em: 26 de setembro de 2007

Por Jim Travis, Microsoft Corporation

Aplica-se a:

  • Microsoft Windows Media Player 10 SDK

  • Microsoft Windows Media Format 9.5 SDK

  • Microsoft Windows Media Rights Manager 10 SDK

  • Microsoft Windows Media Device Manager 10 SDK

Conteúdo

Nesta página

Introdução
O Modelo de Negócios do Cenário
A Arquitetura da Loja On-line
Lojas On-line no Windows Media Player 10
O Plug-in Windows Media Player 10
Conectando o Plug-in à Interface com Usuário
Trabalhando com Licenças
Entendendo as Licenças
Emitindo Licenças
Pré-distribuindo Licenças com Conteúdos
Atualizando Licenças em Background
Preparando Licenças para Sincronização
Medindo o Uso de Conteúdos
Para Mais Informações

Resumo: O Windows Media Player 10 oferece suporte à integração de lojas on-line. Este artigo apresenta um passo a passo com os aspectos técnicos envolvendo vários Windows Media SDKs para implementar uma loja on-line Windows Media Player 10 que oferece conteúdo digital com base em assinaturas periódicas.

Introdução

A demanda de consumidores por músicas e vídeos digitais tem sido crescente. Atualmente, computadores pessoais podem reproduzir fielmente sons e imagens, oferecendo níveis de qualidade superiores às tecnologias analógicas do passado. Uma grande variedade de dispositivos portáteis também permite que os usuários apreciem músicas e vídeos digitais a qualquer momento, em qualquer lugar. Esses dispositivos podem armazenar milhares de músicas, somando várias horas de canções, programas de TV ou filmes completos, mantendo o tamanho e o peso do hardware pequeno.

A Internet provê um mecanismo natural para a distribuição de conteúdo digital. Conexões de banda larga estão mais acessíveis aos consumidores, e as tecnologias de compressão de áudio e vídeo ajudam a reduzir os tamanhos dos arquivos e, conseqüentemente, o tempo de transferência. Entretanto, criar um negócio que ofereça conteúdo digital na Internet apresenta alguns desafios. A pirataria é um problema real, uma vez que os usuários podem criar várias cópias de um arquivo digital rapidamente, sem perda de qualidade, e facilmente distribuir o conteúdo pirateado a várias pessoas.

O Windows Media DRM (digital rights management, ou Gestão de Direitos Digitais) provê uma solução que ajuda a proteger e distribuir mídias digitais com segurança para serem reproduzidos em um computador, dispositivos portáteis ou dispositivos de rede. Uma licença associada à mídia digital define como o conteúdo deve ser protegido, e um conteúdo licenciado funcionará apenas com softwares e equipamentos certificados. O Windows Media DRM 10 provê uma grande flexibilidade em como se emitir licenças e permite personalizar direitos de reprodução e cópia de mídias digitais para uma grande variedade de cenários de negócios.

O Windows Media Player 10 suporta o Windows Media DRM 10 e oferece a Provedores de Conteúdos via Internet uma arquitetura para a integração de lojas on-line de mídias digitais com o Windows Media Player. Dessa forma, os usuários podem visualizar informações ricas e completas sobre as mídias, comprá-las, reproduzi-las, transferi-las para um dispositivo portátil e gravá-las em CD, tudo através do Windows Media Player 10.

Uma maneira pela qual os provedores de lojas on-line podem oferecer conteúdo é simplesmente vendendo licenças abertas. Nesse cenário, os usuários pagam uma única tarifa para aproveitar acesso ilimitado à reprodução do material (embora ainda seja possível limitar quantas vezes o arquivo possa ser copiado). Este é o equivalente de loja on-line às lojas de música de varejo.

Uma outra maneira consiste em oferecer conteúdo por meio de assinaturas. No modelo de assinaturas, os usuários pagam uma tarifa periódica, tipicamente mensal, para apreciar o conteúdo de um catálogo on-line de forma limitada. Se o usuário decidir não continuar com a assinatura, as licenças para todos os conteúdos transferidos simplesmente expiram, desabilitando sua reprodução.

Este artigo provê um passo a passo técnico para a implementação de uma loja on-line de exemplo construída sobre o modelo de assinaturas. São discutidas questões relacionadas à emissão de licenças, manutenção de licenças para os assinantes e registro do uso dos conteúdos (medições). Esses tópicos serão ensinados no contexto de criação de um objeto COM (Component Object Model) para Loja On-line Windows Media Player 10, ou simplesmente plug-in, que permite a integração de códigos DRM com os processos do Windows Media Player.

Além do Windows Media Player 10, as seguintes tecnologias são usadas para criar o serviço de assinaturas:

  • Windows Media Player 10 Software Development Kit (SDK)

  • Windows Media Rights Manager 10 SDK

  • Windows Media Format 9.5 SDK

  • Windows Media Device Manager 10 SDK

  • C/C++

  • COM

  • Active Template Library (ATL)

  • Hypertext Markup Language (HTML)

  • Microsoft Visual Basic Scripting Edition (VBScript)

  • Microsoft JScript

  • Microsoft Internet Information Services (IIS)

  • Active Server Pages (ASP).

Vale ressaltar que códigos DRM no servidor requerem o sistema operacional Microsoft Windows Server 2003, e códigos no cliente requerem o Microsoft Windows XP ou Microsoft Windows Server 2003 Service Pack 1 (SP1).

Os tópicos apresentados neste artigo são aplicáveis para o Windows Media DRM 10 e os dispositivos que o suportam. Este artigo não discute questões relacionadas a dispositivos legados ou versões anteriores do Windows Media Rights Manager.

Para hospedar uma loja on-line Windows Media Player 10, as empresas precisam atender certos critérios e assinar um acordo com a Microsoft. Consulte a página da Microsoft (https://www.microsoft.com/windows/windowsmedia/default.aspx, em inglês) para maiores detalhes.

Nota Sobre Segurança

Na criação de uma loja on-line baseada em assinaturas, a segurança deve ter alta prioridade.

Enquanto o Windows Media DRM ajuda a proteger as mídias digitais, as empresas ainda têm a responsabilidade de garantir que o código desenvolvido é seguro.

Este artigo não contempla questões de segurança. Seu objetivo é mostrar como os vários componentes Windows Media funcionam juntos. Os exemplos apresentados não incluem rotinas adicionais para garantir a segurança dos conteúdos ou da loja on-line. Por exemplo, os códigos de exemplo não realizam autenticação de usuários nem implementam a proteção de informações enviadas pela internet.

Para aprender mais sobre segurança, consulte o Developer Center de Segurança da Microsoft na página MSDN (https://www.microsoft.com/brasil/msdn/seguranca/default.mspx, em português, e https://msdn.microsoft.com/security/, em inglês).

O Modelo de Negócios do Cenário

No modelo baseado em assinaturas, há uma grande variedade de decisões de negócio a serem tomadas. Está fora do escopo deste artigo explorar todas as possibilidades de combinações para essas decisões. Para uma visão geral da utilização de DRM em vários modelos de negócios, consulte o tópico “Implementing Different Business Models” na documentação do Windows Media Rights Manager 10 SDK.

O restante deste artigo assume o seguinte modelo de negócios:

  • Usuários pagam uma tarifa mensal para acessar a loja on-line;

  • Quando os usuários realizam o download de conteúdos, a loja on-line provê a licença requerida ao mesmo tempo;

  • Usuários podem reproduzir os conteúdos quantas vezes desejarem até que uma data de expiração seja alcançada;

  • Usuários podem copiar conteúdos para dispositivos portáteis cinco vezes. Se um usuário tentar copiar para um sexto dispositivo, a licença é concedida dinamicamente como uma cortesia;

  • Usuários podem reproduzir conteúdos de forma compartilhada durante uma sessão peer-to-peer;

  • Usuários não têm permissão de copiar conteúdos para um CD;

  • Usuários não podem fazer backup ou recuperar licenças;

  • A loja on-line atualiza em background as licenças sempre que possível. Quando os usuários pagam sua taxa para o próximo mês, a transição entre os meses deve parecer suave. Fazer uma assinatura com uma caixa de diálogo de nova aquisição porque a licença já expirou não é desejável;

  • Licenças são sempre emitidas para assinantes ativos quando solicitadas. Na prática, pode-se configurar uma política diferente. Para aprender sobre cenários nos quais pode ser necessário reemitir licenças, veja o tópico “Determining Your Policies on Reissuing Licenses” na documentação do Windows Media Rights Manager 10 SDK;

  • Para aumentar a segurança, arquivos de mídias digitais só podem ser reproduzidos por tocadores individualizados. Consulte o tópico “Requiring Individualized Players” na documentação do Windows Media Rights Manager 10 SDK;

  • A loja on-line periodicamente recupera dados sobre contagens de reprodução e cópia e utiliza um serviço de agregação de medições para registrar os valores totais.

A Arquitetura da Loja On-line

Para alcançar os objetivos da loja on-line, é necessário usar uma combinação de Windows Media SDKs. Os SDKs funcionam juntos em muitos casos. Por exemplo, o Windows Media Device Manager 10 SDK pode recuperar dados de um dispositivo. Esses dados são então processados pelo Windows Media Rights Manager 10 SDK. Os seguintes SDKs são utilizados pela loja on-line:

  • Windows Media Player 10 SDK. Provê mecanismos para hospedar a interface com usuário da loja on-line dentro da interface do Windows Media Player 10. O Windows Media Player 10 suporta plug-ins para lojas on-line. Um plug-in é um objeto COM que expõe métodos que o Windows Media Player 10 executa para prover suporte personalizado a DRM. Por exemplo, o Windows Media Player 10 chama IWMPSubscriptionService2::prepareForSync cada vez que um arquivo de mídia digital está para ser transferido para um dispositivo portátil. Isso permite que a loja on-line processe a licença DRM antes que a transferência aconteça;

  • Windows Media Rights Manager 10 SDK. Provê funcionalidades para proteger conteúdos, emitir licenças e criar o serviço de agregação de medições;

  • Windows Media Device Manager 10 SDK. Provê o objeto e a interface necessários para requisitar dados de medições de um dispositivo portátil ou um computador e zerar o armazenamento de medições no dispositivo ou computador;

  • Windows Media Format 9.5 SDK. Provê o suporte necessário para pesquisar na base de licenças do cliente por várias informações de status. Por exemplo, pode-se usar o Windows Media Format SDK para determinar se um arquivo de mídia digital tem uma licença de reprodução válida ou não.

O diagrama de arquitetura a seguir apresenta como as peças se encaixam.

ms867139.WMP10_01s(pt-br,MSDN.10).jpg

Na máquina cliente, todas as atividades da loja on-line ocorrem no Windows Media Player 10. A interface de usuário da loja on-line é apresentada em painéis e janelas especiais no Player. Tarefas relacionadas a DRM, como checar por licenças expiradas nos arquivos do computador do usuário, são realizadas pelo plug-in da loja on-line. Algumas dessas tarefas requerem que o plug-in envie requisições a um ou mais servidores Windows Server 2003 na Internet.

Do lado servidor, o IIS (Internet Information Services) serve páginas Web que provêem a interface de usuário da loja on-line. Páginas ASP criam objetos Windows Media Rights Manager 10 para lidar com as requisições de licenças e realizar tarefas de agregação de medições.

A loja on-line também precisa utilizar tecnologias de banco de dados, como Microsoft SQL Server, para manter informações sobre contas de usuários, mídias digitas e estatísticas de medições (Prover informação detalhada sobre o uso de tecnologias de banco de dados está fora do escopo deste artigo. Sempre que o uso desse tipo de tecnologia for necessário em um exemplo, o código de exemplo executará uma função simbólica e o texto associado irá descrever o objetivo da função.).

Lojas On-line no Windows Media Player 10

De um ponto de vista tecnológico, hospedar uma loja on-line no Windows Media Player 10 é simples.

Quando o Windows Media Player 10 é executado, o programa recupera uma lista de lojas on-line de um servidor mantido pela Microsoft. Cada loja on-line na lista provê uma URL que aponta para um documento especial XML denominado ServiceInfo. O ServiceInfo descreve a loja on-line. O documento contém URLs que apontam para páginas Web, que o Windows Media Player 10 apresenta em um controle de navegação em algumas partes da interface com usuário do Player.

No documento ServiceInfo, o elemento ServiceTask1 descreve o primeiro, ou o mais à esquerda, painel de até três painéis que a loja on-line pode prover. O código de exemplo a seguir mostra como um elemento ServiceTask1 seria implementado no documento ServiceInfo de um serviço de música fictício chamado Proseware.

<ServiceTask1 URL = "https://www.proseware.com/service/html/Music.asp">
	<ButtonText>Proseware\nMusic</ButtonText> 
	<ButtonTip>Proseware Music Store</ButtonTip>
</ServiceTask1> 

Note que o atributo URL aponta para uma página Web que será apresentada no painel do próprio Player. Os elementos filhos, ButtonText e ButtonTip, especificam o texto que aparece na guia acima do painel e o texto da janela de informações (ToolTip) associada à guia.

O documento ServiceInfo também contém outras informações, como textos descritivos e URLs para imagens que o Windows Media Player 10 exibe para identificar a loja on-line. Para informações completas sobre hospedagem de uma loja on-line no Windows Media Player 10, consulte a seção “Windows Media Player 10 Online Stores” do Windows Media Player 10 SDK.

O Plug-in Windows Media Player 10

Criar um plug-in Windows Media Player 10 para uma loja on-line é opcional, mas essa opção oferece algumas vantagens. Primeiramente, o plug-in precisa ser instalado no computador do usuário, o que significa que é possível executar código C++ nativo. Isso torna possível se beneficiar de funcionalidades específicas de C++, como usar o Windows Media Device Manager 10 SDK para trabalhar com dispositivos portáteis.

Em segundo lugar, o plug-in é estreitamente integrado com o Windows Media Player 10. Isso significa que o Player instancia o plug-in nos momentos apropriados, como quando o usuário tenta reproduzir conteúdo fornecido. O Player também executa métodos específicos implementados pelo plug-in, basicamente para permitir a realização de tarefas relacionadas a DRM. Mais adiantes neste artigo, serão apresentados códigos de exemplo que ilustram o uso de alguns métodos das interfaces com o plug-in IWMPSubscriptionService e IWMPSubscriptionService2.

  • startBackgroundProcessing e stopBackgroundProcessing. O Windows Media Player 10 executa esses métodos para permitir a realização de tarefas de processamento em background necessárias para a loja on-line. O Player usa essas chamadas para iniciar e parar as tarefas de background de modo a permitir que o código da loja seja executado quando o Player não está realizando seu próprio processamento background. O código de exemplo neste artigo usa essas chamadas para gerenciar o processo que atualiza licenças em background.

  • prepareForSync. O Windows Media Player 10 executa esse método para permitir o processamento de um arquivo de mídia digital antes de ele ser transferido para um dispositivo portátil. O código de exemplo neste artigo usa essa chamada para inspecionar a licença associada ao conteúdo e fazer modificações quando apropriado.

  • deviceAvailable. O Windows Media Player 10 executa esse método depois que a sincronização com um dispositivo portátil é completada se o tempo decorrido desde a última chamada é uma semana ou mais. O código de exemplo neste artigo usa essa função para recuperar dados de medições do dispositivo.

Há métodos adicionais que podem ser escolhidos para serem implementados. Também é possível especificar quais métodos o Windows Media Player 10 chama marcando uma chave no registro. Para mais informações sobre os métodos de IWMPSubscriptionService, IWMPSubscriptionService2 e chaves de registro de lojas on-line, consulte o Windows Media Player 10 SDK.

O pacote de instalação do Windows Media Player 10 SDK adiciona um assistente de projeto para o Visual Studio .NET 2003 que gera um plug-in para loja on-line no seu projeto. Para usar o assistente, é necessário seguir as instruções na seção “Building the COM Component” da documentação do Windows Media Player 10 SDK. Criar o plug-in dessa maneira oferece um bom ponto de partida para o seu próprio plug-in. O código gerado pelo assistente inclui implementações das interfaces IWMPSubscriptionService and IWMPSubscriptionService2. Também é fornecida a infra-estrutura necessária para criar e registrar um componente COM usando ATL.

O projeto também realiza o trabalho para ter certeza de que o plug-in se registra como um plug-in Windows Media Player 10 de loja on-line para o seu ID de distribuidor de conteúdo. O ID de distribuído de conteúdo é a string usada para identificar sua loja on-line e marcar seu conteúdo no campo WM/ContentDistributor do cabeçalho do arquivo Windows Media (ou o cabeçalho DRM).

Conectando o Plug-in à Interface com Usuário

Embora a maioria das tarefas para as quais tipicamente se deseja usar um plug-in ocorram em background, pode-se decidir conectar seu plug-in à interface com usuário da sua loja on-line. Por exemplo, suponha que seu modelo de negócios especifique que os usuários são autorizados a reproduzir seus conteúdos em apenas um computador por vez. Pode-se então querer prover um botão no qual os usuários podem clicar para desautorizar um computador em particular antes de tentar adquirir licenças para um outro. Para fazer isso, seria necessário usar o Windows Media Format 9.5 SDK para revogar todas as licenças no primeiro computador. Seu plug-in pode realizar esta tarefa. Para mais informações sobre revogação de licenças, consulte “Implementing License Revocation with Windows Media DRM 10” na página da Microsoft (https://www.microsoft.com/windows/windowsmedia/knowledgecenter/technicalarticles.aspx#digitalrightsmanagement).

As páginas Web que compreendem a interface com usuário de sua loja on-line podem instanciar seu plug-in. Uma maneira comum de fazer isso é usando o elemento HTML OBJECT. O código de exemplo HTML a seguir mostra o elemento OBJECT em uso. Observe que o ID de classe (classid) mostrado está preenchido com a letra “x”. Na prática, seria informado o GUID real para o ID de classe de seu objeto.

<OBJECT id = "plugin" height = 0 width = 0
	classid = "CLSID:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"> 
</OBJECT> 

Entretanto, para seu plug-in se comunicar com o código de script na página Web, ele deve expor uma interface derivada de IDispatch. O assistente de criação do plug-in da loja on-line não provê essa implementação. Embora uma discussão detalhada sobre automação COM esteja fora do escopo deste artigo, pode-se usar os seguintes passos como um guia.

  1. Adicione um arquivo Interface Definition Language (IDL) no projeto de seu plug-in. O IDL deve definir uma interface personalizada para seu plug-in e uma biblioteca de tipos. O código de exemplo a seguir mostra um IDL simples que define uma interface dual personalizada derivada de IDispatch. A interface contém um único método, denominado revokeAll. Observe que GUIDs para Interface Identifier (IID), Type Library Identifier (LIBID) e Class Identifier (CLSID) são preenchidos com a letra “x”. Na prática, eles conteriam valores únicos de GUID gerados por você.
import "oaidl.idl";
import "ocidl.idl";

[
    object,
    uuid(xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx),
    dual,
    helpstring("IProsewarePlugin interface"),
    pointer_default(unique)
]
interface IProsewarePlugin : IDispatch
{
    [id(1), helpstring("Revokes all licenses")] HRESULT revokeAll();
};

[
    uuid(xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx),
    version(1.0),
    helpstring("ProsewarePlugin 1.0 Type Library")
]
library ProsewareLib
{
    importlib("stdole32.tlb");
    importlib("stdole2.tlb");

    [
        uuid(xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx),
        helpstring("ProsewarePlugin Class")
    ]
    coclass ProsewarePluginClass
    {
        [default] interface IProsewarePlugin;
    };
};
  1. Derive a classe de seu plug-in de IDispatchImpl. Essa classe ATL provê uma implementação padrão para a parte de IDispatch de sua interface personalizada. O código de exemplo a seguir mostra como isso pode ser feito.
class ATL_NO_VTABLE CProsewarePlugin :
    public CComObjectRootEx<CComSingleThreadModel>,
    public CComCoClass<CProsewarePlugin, &CLSID_ProsewarePlugin>,
    public IWMPSubscriptionService2,
    public IDispatchImpl<IProsewarePlugin, &IID_IProswarePlugin,
                                           &LIBID_ProsewareLib>
{ 
    // Wizard created class implementation goes here.
}

Note que o CLSID, IID e LIBID devem ser definidos no projeto de seu plug-in, ou explicitamente ou incluindo o arquivo de saída correto do compilador MIDL.

  1. Atualize o mapa COM. O código de exemplo a seguir mostra como isso pode ser feito.
BEGIN_COM_MAP(CProsewarePlugin)
    COM_INTERFACE_ENTRY(IWMPSubscriptionService)
    COM_INTERFACE_ENTRY(IWMPSubscriptionService2)
    COM_INTERFACE_ENTRY(IProsewarePlugin)
    COM_INTERFACE_ENTRY(IDispatch)
END_COM_MAP()
  1. Implemente os métodos de sua interface personalizada.

  2. Atualize o arquivo .rgs no projeto de seu plug-in para registrar sua biblioteca de tipos.

Uma vez que seu plug-in expõe sua interface personalizada dessa maneira, pode-se usar código de script na sua página Web para chamar seu método personalizado. O código de exemplo JScript a seguir demonstra isso.

// Call the revokeAll() method of the
// object named "plugin"
plugin.revokeAll();

Importante: Tenha cuidado ao expor métodos de seu plug-in.

Seu objeto COM pode ser instanciado por qualquer um que crie uma página Web. Isso significa que páginas Web maliciosas podem executar os métodos que seu plug-in expõe. Deve-se planejar considerando essa possibilidade para proteger a segurança de sua loja on-line. Por exemplo, o método revokeAll criado no exemplo anterior não deve revogar automaticamente todas as licenças quando executado. No mínimo, deve-se questionar o usuário por permissão para revogar as licenças.

Ao invés de utilizar uma instância separada de seu plug-in na sua página Web, pode-se acessar a mesma instância usada pelo Windows Media Player 10. Para fazer isso, pode-se fazer o objeto de seu plug-in uma instância única (COM singleton). Lembramos que um singleton é um objeto COM que pode ser criado exatamente uma única vez em cada processo. O primeiro cliente que usar CoCreateInstance para instanciar um singleton faz com que a classe fábrica de objetos singleton crie o objeto. Quando clientes subseqüentes no mesmo processo criam o objeto singleton, eles recebem um ponteiro para a instância já existente – não são criadas cópias adicionais do objeto.

Para fazer seu plug-in um COM singleton, basta adicionar a seguinte macro ATL ao código de sua classe.

DECLARE_CLASSFACTORY_SINGLETON(CProsewarePlugin)

Também é possível criar elementos de interface com usuário no seu plug-in. Dessa forma, quando seu plug-in é embutido em uma página Web, sua interface com usuário se torna visível na página Web. Isso expõe diretamente seu plug-in ao usuário, ao invés de conectar elementos HTML ao plug-in usando scripts. Para fazer isso, é necessário compreender controles ActiveX e como eles expõem elementos de interface.

Trabalhando com Licenças

Um dos desafios de criar uma loja on-line baseada em assinaturas é gerenciar as licenças. Os usuários podem baixar vários arquivos de mídia digital ao longo do tempo. Como as licenças podem expirar, é importante considerar estratégias para manter as licenças atuais de forma que os usuários não percebam lacunas no serviço. Se um usuário pagou sua tarifa de assinatura para o próximo mês, faz sentido renovar as licenças antes do fim do mês corrente, evitando assim a necessidade de adquirir uma licença quando o usuário tentar reproduzir o conteúdo. Da mesma maneira, deve-se evitar ter licenças expiradas no dispositivo portátil do usuário.

Entendendo as Licenças

Uma nova funcionalidade do Windows Media Rights Manager 10 SDK é a chamada cadeia de licença (license chain). Cadeias de licença permitem dividir as licenças em duas partes: licenças raiz e folhas. Para o modelo de assinatura, isso significa que a loja on-line pode rapidamente renovar uma assinatura reemitindo uma única licença raiz. Cada item de conteúdo é associado a uma licença folha individual. Cada licença folha contém um uplink ID que aponta para a licença raiz usando sua própria ID (key ID). É a combinação dos privilégios na licença raiz com os privilégios na licença folha que determinam se o usuário pode ou não acessar o conteúdo.

Observe que usar uma cadeia de licença para proteger o conteúdo requer que se especifique os elementos da cadeia de licença adicionando o uplink ID correto a cada licença. Para maiores detalhes, consulte “Specifying a License Chain When Protecting Content” na documentação do Windows Media Rights Manager 10 SDK.

Licença pré-distribuída é um método de distribuir uma licença antes que o conteúdo protegido seja acessado. Faz sentido pré-distribuir licenças no modelo de assinaturas porque o usuário sempre será autenticado antes de ser permitido a baixar conteúdo protegido. A pré-distribuição faz o processo de baixar e acessar o conteúdo parecer suave porque elimina atrasos futuros que ocorrem ao adquirir licenças, por exemplo, durante a primeira tentativa de reprodução. A loja on-line de exemplo usa pré-distribuição para emitir as licenças raiz e folhas iniciais assim como para reemitir licenças.

Para pré-distribuir uma licença no exemplo, é necessário conhecer as seguintes informações.

  • Root key ID . O valor do ID chave usado para gerar a chave da licença raiz. No exemplo, há uma licença raiz, então há um único ID chave raiz que será informado estaticamente no código (hard-coded).

  • Leaf key ID . O valor do ID chave usado para gerar as chaves das licenças folhas. Cada item de conteúdo é associado a uma licença folha, então cada item possuirá seu próprio ID chave que será recuperado dinamicamente.

  • License key seed . O valor informado com o ID chave para gerar uma chave. Pode-se usar o mesmo valor semente para todas as chaves. Esse valor é estático (hard-coded).

  • Content ID . Cada item de conteúdo deve possuir um identificador único que é armazenado no cabeçalho DRM. O ID de conteúdo será usado para recuperar o ID chave da folha corrente antes de emitir uma licença folha para um arquivo de mídia digital em particular. Esse valor será recuperado do cabeçalho DRM no cliente e enviado como um parâmetro para a página ASP que emite a licença.

  • Content server public key . Esse valor é informado pelo distribuidor de conteúdo.

  • License revocation public key . Esse valor é adicionado às licenças para permitir-lhe revogá-las todas. Esse valor é informado estaticamente (hard-coded).

  • User ID . Esse valor é adicionado às licenças para permitir a revogação para um usuário em particular.

  • Individualization version . Quando se recebe uma requisição de licença, esse valor será recuperado do cabeçalho do conteúdo e usado para marcar a propriedade WMRMLicGen.IndividualizationVersion.

  • Client version . Quando se recebe uma requisição de licença, esse valor será recuperado do objeto WMRMChallenge e usado para determinar se o número de versão do cliente para o subsistema Windows Media DRM é 10.

  • Metering certificate . Essa string é usada para autorizar o Windows Media DRM a armazenar dados de medições para os conteúdos providos.

Pode-se ver esses valores em uso na próxima seção.

Emitindo Licenças

Emite-se licenças usando objetos do SDK do Windows Media Rights Manager 10 em uma página ASP. O código de exemplo a seguir cria uma página ASP que usa VBScript para pré-distribuir licenças para a loja on-line de exemplo baseada em assinaturas. Note que para suportar aquisição regular (compra sem assinatura) de licenças seria necessário código adicional.

Pode-se também notar que esse código não realiza autenticação de usuário. Na prática, deve-se seguir passos adicionais para garantir que as licenças são emitidas apenas para assinantes válidos.

<%
Response.Buffer = True
Response.Expires = 0
On Error Resume Next

' """""""""""""""""""""""""""""""""""""""""""""""""""""
' Declare variables.
' """""""""""""""""""""""""""""""""""""""""""""""""""""
Dim ChallengeObj           ' WMRMChallenge object
Dim HeaderObj              ' WMRMHeader object    
Dim KeyObj                 ' WMRMKeys object
Dim RightsObj              ' WMRMRights object
Dim RestrictObj            ' WMRMRestrictions object
Dim LicenseObj             ' WMRMLicGen object
Dim ResponseObj            ' WMRMResponse object

Dim ContentDistributor     ' Online store key name.
Dim ClientVersionInfo      ' Version of the client
Dim ContentID              ' Content ID
Dim ContentServerPubKey    ' Public key of the content server
Dim Delivery               ' Delivery flag
Dim IndiVersion            ' Security version of the DRM component
Dim LeafKey                ' Key for the leaf license
Dim LeafKID                ' Key ID of the leaf license
Dim RootKey                ' Key for the root license
Dim RootKID                ' Key ID of the root license
Dim License                ' License to deliver
Dim Rights                 ' Rights string for the license
Dim LicResponse            ' License response
Dim LicRevPubKey           ' Public key for license revocation
Dim PlayRestrictions       ' Playback restrictions
Dim CopyRestrictions       ' Copy restrictions
Dim Seed                   ' License key seed
Dim strLicenseRequested    ' License request string
Dim varClientInfo          ' Client information
Dim Licensetype            ' Flag for root or leaf license
Dim UserID                 ' User ID of the client
Dim NeedsCopies            ' License requested for additional copy count
Dim CopyCount              ' Number of copies to grant
Dim MeterCert              ' Metering certificate

Do

' """""""""""""""""""""""""""""""""""""""""""""""""""""
' Set variables.
' """""""""""""""""""""""""""""""""""""""""""""""""""""
' Variables passed by query string parameters.
ContentID = Request("cid")
UserID = Request("uid")
NeedsCopies = Request("NeedsCopies")

RootKID = "<Replace this with the key ID for the root.>"
ContentDistributor = "Proseware"
Delivery = "" 
ContentServerPubKey = "<Replace this with the content server public key.>"
Seed = "<Replace this with the key seed.>"
LicRevPubKey = "<Replace this with the license revocation public key.>"
MeterCert = "<Replace this with the metering certificate.>"

' """""""""""""""""""""""""""""""""""""""""""""""""""""
' Determine the type of license requested.
' """""""""""""""""""""""""""""""""""""""""""""""""""""
If (ContentID = "root") Then
    Licensetype = "root"
Else
    Licensetype = "leaf"

    ' Retrieve the key ID for the leaf license.
    ' GetLeafKID() is a custom function not implemented
    ' in this example. In practice, this function would 
    ' retrieve the key ID from a database.
    LeafKID = GetLeafKID(ContentID)

    ' If the leaf is requested with the NeedsCopies flag set to true
    ' grant 1 additional right to copy.
    ' If the flag is set to false, then issue a new leaf with a
    ' copy count of 5.
    If NeedsCopies = "true" Then
        CopyCount = 1
    Else
        CopyCount = 5
    End If

End If

' """""""""""""""""""""""""""""""""""""""""""""""""""""
' Create objects.
' """""""""""""""""""""""""""""""""""""""""""""""""""""
Set ChallengeObj = Server.CreateObject("WMRMObjs.WMRMChallenge")
Set HeaderObj = Server.CreateObject("WMRMObjs.WMRMHeader")
Set KeyObj = Server.CreateObject("WMRMObjs.WMRMKeys")
Set RightsObj = Server.CreateObject("WMRMObjs.WMRMRights")
Set RestrictObj = Server.CreateObject("WMRMObjs.WMRMRestrictions")    
Set ResponseObj = Server.CreateObject("WMRMObjs.WMRMResponse")
Set LicenseObj = Server.CreateObject("WMRMObjs.WMRMLicGen")

' """""""""""""""""""""""""""""""""""""""""""""""""""""
' Get the license challenge.
' """""""""""""""""""""""""""""""""""""""""""""""""""""
strLicenseRequested = Request("challenge")
    If (strLicenseRequested = "") Then Exit Do

ChallengeObj.Challenge = strLicenseRequested
    If (Err.Number <> 0) Then Exit Do

varClientInfo = ChallengeObj.ClientInfo
    If (Err.Number <> 0) Then Exit Do

HeaderObj.Header = ChallengeObj.Header
    If (Err.Number <> 0) Then Exit Do

' Verify the header with the public key
    lResult = HeaderObj.Verify(ContentServerPubKey)
    If (lResult = 0) Then Exit Do

IndiVersion = HeaderObj.IndividualizedVersion
    If (Err.Number <> 0) Then Err.clear

' GetCurrentIndiv() is a placeholder function for a custom
' routine that retrieves the minimum inidividualization 
' version you want to support.
If IndiVersion < GetCurrentIndiv() Then Exit Do

' """""""""""""""""""""""""""""""""""""""""""""""""""""
' Check the version of the client player.
' """""""""""""""""""""""""""""""""""""""""""""""""""""
ClientVersionInfo = ChallengeObj.ClientVersion
    If (Err.Number <> 0) Then Exit Do

ClientVersionArray = Split(ClientVersionInfo, ".")
    If (ClientVersionArray(0) < 10) Then 
        ' The user needs to upgrade the Player version.
        Exit Do
    End If

' """""""""""""""""""""""""""""""""""""""""""""""""""""
' Generate the keys. A root key is required for both
' leaf and root licenses.
' """""""""""""""""""""""""""""""""""""""""""""""""""""
KeyObj.Seed = Seed
    If (Err.Number <> 0) Then Exit Do

If (Licensetype = "leaf") Then 
    KeyObj.KeyID = LeafKID
        If (Err.Number <> 0) Then Exit Do

    LeafKey = KeyObj.GenerateKey()
        If (Err.Number <> 0) Then Exit Do
End If

KeyObj.KeyID = RootKID
    If (Err.Number <> 0) Then Exit Do

RootKey = KeyObj.GenerateKeyEx(8)
    If (Err.Number <> 0) Then Exit Do

' """""""""""""""""""""""""""""""""""""""""""""""""""""
' Set the rights for leaf or root licenses.
' """""""""""""""""""""""""""""""""""""""""""""""""""""
    RightsObj.AllowPlay = True
    RightsObj.AllowCopy = True
    RightsObj.AllowCollaborativePlay = True
    RightsObj.AllowBackupRestore = False
    RightsObj.AllowPlaylistBurn = False
    RightsObj.MinimumClientSDKSecurity = 1000
    RightsObj.MinimumSecurityLevel = 1000

If (Licensetype = "leaf") Then    ' Set rights for a leaf license
    RightsObj.CopyCount = CopyCount

    ' Set copy restrictions.
    Call RestrictObj.AddRestriction(6, 0)     
    CopyRestrictions = RestrictObj.GetRestrictions
    RightsObj.CopyRestrictions = CopyRestrictions
        If (Err.Number <> 0) Then Exit Do

Elseif (Licensetype = "root") Then    ' Set rights for a root license
    ' GetExpirationDateUser() is a placeholder function that retrieves
    ' the next expiration date for the current subscriber's account.
    RightsObj.ExpirationDate = GetExpirationDateUser(UserID)
    RightsObj.DeleteOnClockRollback = False
    RightsObj.DisableOnClockRollback = True

End If

Rights = RightsObj.GetAllRights()
    If (Err.Number <> 0) Then Exit Do

'"""""""""""""""""""""""""""""""""""""""""""""""""""""
' Generate the license.
'"""""""""""""""""""""""""""""""""""""""""""""""""""""
If (Licensetype = "leaf") Then 
    LicenseObj.KeyID = LeafKID
        If (Err.Number <> 0) Then Exit Do

    Call LicenseObj.SetKey("", LeafKey)
        If (Err.Number <> 0) Then Exit Do
Elseif (Licensetype = "root") Then
    LicenseObj.KeyID = RootKID
        If (Err.Number <> 0) Then Exit Do

    Call LicenseObj.SetKey("", RootKey)
        If (Err.Number <> 0) Then Exit Do
End If

' Only meter on the leaf license.
If(LicenseType = "leaf") Then
    LicenseObj.MeteringCertificate = MeterCert
        If (Err.Number <> 0) Then Exit Do
End If

LicenseObj.Attribute("ContentDistributor") = ContentDistributor
    If (Err.Number <> 0) Then Exit Do

' UID and LGPUBKEY are needed only when you want to support
' license revocation.
LicenseObj.Attribute("UID") = UserID
    If (Err.Number <> 0) Then Exit Do
LicenseObj.Attribute("LGPUBKEY") = LicRevPubKey 
    If (Err.Number <> 0) Then Exit Do

LicenseObj.Rights = Rights
    If (Err.Number <> 0) Then Exit Do

LicenseObj.ClientInfo = varClientInfo
    If (Err.Number <> 0) Then Exit Do

LicenseObj.IndividualizedVersion = IndiVersion
    If (Err.Number <> 0) Then Exit Do

' GetNextPriorityUser is a placeholder function
' that retrieves the correct priority value. 
LicenseObj.Priority = GetNextPriorityUser(UserID)
    If (Err.Number <> 0) Then Exit Do

LicenseObj.BindToPubKey = ContentServerPubKey
    If (Err.Number <> 0) Then Exit Do

If (Licensetype = "leaf") Then  
    ' Link to the root license
    ' for license chaining.
    LicenseObj.UplinkKid = RootKid
    LicenseObj.UplinkKey = RootKey
        If (Err.Number <> 0) Then Exit Do
End If

License = LicenseObj.GetLicenseToDeliver
    If (Err.Number <> 0) Then Exit Do

Delivery = "deliver"

Loop While False

' This page handles pre-delivery only.
If (Delivery = "deliver") Then
    Call ResponseObj.AddLicense("2.0.0.0", License)
    LicResponse = ResponseObj.GetLicenseResponse()
    Response.Write LicResponse
End if

' """""""""""""""""""""""""""""""""""""""""""""""""""""
' Clear the objects.
' """""""""""""""""""""""""""""""""""""""""""""""""""""
Set ChallengeObj = Nothing
Set HeaderObj = Nothing
Set KeyObj = Nothing
Set RightsObj = Nothing
Set RestrictObj = Nothing
Set LicenseObj = Nothing
Set ResponseObj = Nothing

%>

Note que esse código de exemplo pré-distribui licenças que suportam medições. Será detalhado mais sobre medições em uma seção mais adiante.

A função simbólica GetCurrentIndiv recupera um valor de versão de individualização de um banco de dados. É importante manter a versão de individualização atualizada. Para informações sobre a última versão de individualização, consulte a página da Microsoft (http://licenseserver.windowsmedia.com/).

A função simbólica GetExpirationDateUser recupera uma data de um banco de dados. Esse valor deve corresponder à data de expiração da conta do assinante.

A função simbólica GetNextPriorityUser recupera um valor de prioridade. Esse valor deve ser incrementado uma vez por mês para cada usuário. Utilizar valores de prioridade crescentes garante que o usuário sempre tem a licença mais recentemente emitida no seu dispositivo móvel. Consulte a documentação para WMRMLicGen.Priority no Windows Media Rights Manager 10 SDK para maiores informações

Pré-distribuindo Licenças com Conteúdos

Agora que já se sabe emitir licenças, podemos escrever código para fazer requisições de licenças.

Pode-se usar código HTML e JScript na página Web de sua loja on-line para pré-distribuir licenças para conteúdos baixados pelos usuários. Para fazer isso, sua página Web deve embutir o objeto RMGetLicense. A página Web de exemplo a seguir embute esse objeto.

<HTML>
<BODY>
Simple Web page that embeds the RMGetLicense object

<OBJECT name = RMGetLicense
        id = RMGetLicense
        classid = clsid:A9FC132B-096D-460B-B7D5-1DB0FAE0C062
        height = 0 width = 0>
</OBJECT>

</BODY>
</HTML>

Para pré-distribuir uma licença quando baixando conteúdo para o computador do usuário, pode-se usar código como o exemplo JScript a seguir.

<SCRIPT Language = "JScript">
    // Pre-deliver a root or leaf license.
    // cid is the content ID for the digital media
    // file being downloaded. A value of "root" will
    // cause a root license to be issued.
    // uid is a unique user ID.
    function GetLicenseFromURL(cid, uid)
    {
        var PreDelURL = "https://www.proseware.com/Predeliver.asp?";
        PreDelURL += "cid=";
        PreDelURL += cid;
        PreDelURL += "&uid=";
        PreDelURL += uid;

        try
        {
            RMGetLicense.GetLicenseFromURL("", PreDelURL);
        }
        catch
        {
             // GetLicenseFromURL doesn't return error codes.
             // However, calling GetLicenseFromURL can cause
             // an exception to be raised from ouside Windows Media
             // Rights Manager. This empty catch block
             // intentionallydiscards these
             // exceptions so the call fails silently.
        }
    }
</SCRIPT>

Pode ser observado que nenhum valor é anexado à string de requisição para o parâmetro NeedsCopies. Nesse cenário, o código está pré-distribuindo a licença inicial com a transferência do conteúdo, então omitir esse valor causará as licenças folhas a serem emitidas com um contador completo de 5 cópias, o que é o comportamento desejado.

Mantenha em mente que será requisitada a licença raiz apenas se uma ainda não foi emitida para o mês corrente. Isso significa que será necessário rastrear quando licenças raiz são emitidas usando um banco de dados.

Atualizando Licenças em Background

Pode-se executar código no cliente para testar se a licença raiz está para ser expirada. Como parte desse processamento background, é uma boa idéia também checar por licenças folhas perdidas ou danificadas. Essa funcionalidade deve ser incluída no seu plug-in de loja on-line Windows Media Player 10.

Observe que o código descrito nesta seção assume que o usuário individualizou sua instalação do Windows Media Player. Se o Player ainda não foi individualizado, o código de atualização de licenças pode falhar sem exibir mensagens de erros.

O código que atualizará licenças realizará as seguintes tarefas:

  • Criar um processo para realizar o processamento background. Também serão utilizados dois objetos de eventos: um evento para pausar o processo e um segundo evento para sair do processo, quando requisitado.O processo é criado quando o Windows Media Player 10 chama IWMPSubscriptionService::startBackgroundProcessing. O processo pausa quando o Windows Media Player 10 chama IWMPSubscriptionService2::stopBackgroundProcessing. O processo sai quando seu trabalho é finalizado ou quando é levantado o evento para finalizá-lo;

  • Recuperar uma lista de reprodução preenchida com todos os itens de mídia digital na biblioteca Windows Media Player 10 do usuário que foram fornecidos pela sua loja on-line. Isso requer o modelo de objeto do Windows Media Player 10;

  • Para cada item de mídia digital na lista de reprodução, teste se existe uma licença válida e se a licença irá expirar em uma semana. Isso requere tanto o Windows Media Player 10 SDK quanto o Windows Media Format 9.5 SDK;

  • Se a licença a ser reproduzida estiver para ser expirada, requisitar uma licença raiz atualizada e marcar um indicador para evitar a requisição da licença raiz novamente. Se a licença a ser reproduzida for inválida após uma licença raiz ser emitida, requerer uma nova licença folha. Solicitar a reemissão de licenças requer o uso do Windows Media Rights Manager 10 SDK.

O diagrama a seguir mostra o fluxo lógico do código que atualiza licenças em background.

ms867139.WMP10_02(pt-br,MSDN.10).jpg

Verificando uma Licença Válida

Antes de requisitar a atualização de licenças, é necessário checar se há licenças em falta ou próximo de expirarem. Para fazer essa tarefa mais fácil, pode-se criar uma classe auxiliar que encapsula a funcionalidade requerida.

O exemplo a seguir declara uma classe chamada CLicenseExpirationChecker. Tenha em mente quando usar o Windows Media Format SDK que é necessário referenciar os arquivos .lib corretos.

#include <stdio.h>
#include <windows.h>
#include <wmsdk.h> // Windows Media Format 9.5 SDK header
#include <atlbase.h>
#include <atlcom.h>

#define CNS_PER_DAY 864000000000 // 100ns units per day.

class CLicenseExpirationChecker
{
public:
    HRESULT Initialize();
    HRESULT CheckExpiration(BSTR  bstrFileName, 
                            DWORD dwExpirationWindow, 
                            BSTR* pbstrUpdateContentID);

private:
    HRESULT GetContentID(BSTR* pbstrContentID);

    CComPtr<IWMMetadataEditor> m_spEditor;
    CComPtr<IWMDRMEditor>      m_spDRMEditor;
};

O método Initialize contém o código para criar o objeto de edição de meta-dados e executar o método QueryInterface da interface IWMDRMEditor, como o código de exemplo a seguir demonstra.

HRESULT CLicenseExpirationChecker::Initialize()
{
    HRESULT hr = S_OK;

    // Create the metadata editor object.
    hr = WMCreateEditor(&m_spEditor);

    if(SUCCEEDED(hr))
    {
        // Get the DRM metadata editor interface.
        hr = m_spEditor.QueryInterface(&m_spDRMEditor);
    }

    return hr;
}

O método CheckExpiration recebe dois parâmetros de entrada. bstrFileName contém o caminho para o arquivo de mídia digital a ser inspecionado. dwExpirationWindow contém o número de dias sobre os quais o método irá checar a data de expiração. O parâmetro de retorno, pbstrUpdateContentID, é usado para retornar um dos três valores:

  • NULL se a licença for válida. Isso significa que não é necessário mais processamento para o item corrente;

  • “root” se a data de expiração da licença estiver dentro da janela de validade;

  • O valor de ID de conteúdo do cabeçalho DRM se a licença folha estiver perdida ou inválida, ou se ambas licenças, folha e raiz, estiverem perdidas ou inválidas.

O código de exemplo a seguir mostra uma implementação para o método CheckExpiration:

HRESULT CLicenseExpirationChecker::CheckExpiration(
    BSTR  bstrFileName, 
    DWORD dwExpirationWindow, 
    BSTR* pbstrUpdateContentID)
{
    // Check for initialization.
    if(!m_spDRMEditor)
    {
        return NS_E_INVALID_REQUEST; 
    }

    // Parameter checking.
    if(NULL == bstrFileName || 
        0 == SysStringLen(bstrFileName) ||
        NULL == pbstrUpdateContentID)
    {
        return E_INVALIDARG;
    }

    HRESULT hr = S_OK;

    // Time variables to hold the current and expiration times.
    FILETIME           CurrentTime;
    ZeroMemory(&CurrentTime, sizeof(FILETIME));
    FILETIME*          pExpireTime = NULL;
    ULARGE_INTEGER     uliCurrent,
                       uliExpire;
    ZeroMemory(&uliCurrent, sizeof(ULARGE_INTEGER));
    ZeroMemory(&uliExpire, sizeof(ULARGE_INTEGER));
    ULONGLONG          uliDiff = 0; 

    // Variables for retrieving the license state information.
    WM_LICENSE_STATE_DATA*  pWMLicState  = NULL;
    WMT_ATTR_DATATYPE       DataType = WMT_TYPE_DWORD;       
    BYTE* pbData = NULL;
    WORD  cbData = 0;
    BOOL bProtected = FALSE;  
    CComBSTR bstrRoot;
    hr = bstrRoot.Append(L"root");

    if(SUCCEEDED(hr))
    {
        // Ensure we are working with protected content.
        hr = WMIsContentProtected((WCHAR*)bstrFileName, &bProtected);
    } 

    if(SUCCEEDED(hr))
    {
        if(FALSE == bProtected)
        {
            hr = NS_E_INVALID_REQUEST;
        }
    }

    if(SUCCEEDED(hr))
    {
        // Open the file for metadata editing.
        hr = m_spEditor->Open((WCHAR*)(bstrFileName));
    }

    if(SUCCEEDED(hr))
    {
        // Get the current time from the system.
        GetSystemTimeAsFileTime(&CurrentTime);

        // Get the license state data.

        // First get the size of the data.
        hr = m_spDRMEditor->GetDRMProperty(
                   g_wszWMDRM_LicenseState_Playback,
                   &DataType,
                   NULL,
                   &cbData);
    }

    // License state data attributes should always be 
    // a binary data type.
    if(SUCCEEDED(hr) && DataType == WMT_TYPE_BINARY)
    {   
        // Allocate memory for the data.
        pbData = new BYTE[cbData];
        if(pbData == NULL)
        {
            hr = E_OUTOFMEMORY;
        }

        if(SUCCEEDED(hr))
        {
            // Get the actual data.
            hr = m_spDRMEditor->GetDRMProperty(
                        g_wszWMDRM_LicenseState_Playback,
                        &DataType,
                        pbData,
                        &cbData);
        }

        if(SUCCEEDED(hr))
        {
            // Set the license state data pointer.
            pWMLicState = (WM_LICENSE_STATE_DATA*)pbData;

            // Depending on the category of restriction, 
            // there will be a varying number
            // of dates in the license state data.
            switch(pWMLicState->stateData->dwCategory)
            {
            case WM_DRM_LICENSE_STATE_UNTIL:
            case WM_DRM_LICENSE_STATE_COUNT_UNTIL:
                {
                    if(pWMLicState->stateData->dwNumDates != 1)
                    {
                        hr = E_UNEXPECTED;
                        break;
                    }
                    pExpireTime = &pWMLicState->stateData->datetime[0];
                    break;
                }
            case WM_DRM_LICENSE_STATE_FROM_UNTIL:
            case WM_DRM_LICENSE_STATE_COUNT_FROM_UNTIL:
                {
                    if(pWMLicState->stateData->dwNumDates != 2)
                    {
                        hr = E_UNEXPECTED;
                        break;
                    }
                    pExpireTime = &pWMLicState->stateData->datetime[1];
                    break;
                }
            //  If the state category is NORIGHT, then either 
            //  the leaf license or the root license needs 
            //  to be acquired. In this case, set the string to 
            //  the content ID.
            case WM_DRM_LICENSE_STATE_NORIGHT:
                {
                    hr = GetContentID(pbstrUpdateContentID);
                    break;
                }
            default:
                {
                    hr = E_UNEXPECTED;
                    break;
                }
            }

            if(pExpireTime)
            {
                // Copy the FILETIME values to their 
                // ULARGE_INTEGER counterparts.
                memcpy((void*)&uliCurrent, (void*)&CurrentTime, 
                        sizeof(uliCurrent));
                memcpy((void*)&uliExpire, (void*)pExpireTime, 
                        sizeof(uliCurrent));

                // Compare the dates.
                if(uliExpire.QuadPart > uliCurrent.QuadPart)
                {
                    // Get the difference between the current time 
                    // and the expiration time.
                    uliDiff = uliExpire.QuadPart - uliCurrent.QuadPart;

                    // If the difference is less than the number of days 
                    // specified in the call to this function, the file 
                    // needs an update.
                    if(uliDiff <= 
                        (((ULONGLONG)CNS_PER_DAY) * dwExpirationWindow))
                    {
                        hr = bstrRoot.CopyTo(pbstrUpdateContentID);
                    }
                }
                else // Expiration date is in the past.
                {
                    hr = bstrRoot.CopyTo(pbstrUpdateContentID);
                }
            }
        }
    }

// Clean up.
    m_spEditor->Close();

    if(pbData)
    {
        delete[] pbData;
        pbData = NULL;
    }

    pWMLicState = NULL;
    pExpireTime = NULL;

    return hr;
}

A função auxiliar chamada GetContentID recupera o valor do atributo ID de conteúdo do arquivo aberto no editor. O parâmetro de retorno, pbstrContentID, aponta para o endereço de um BSTR que contém a string de ID de conteúdo. Lembre-se que essa string precisará ser liberada mais tarde (No exemplo, a string será retornada usando um CComBSTR que libera a string quando ela estiver fora de escopo. Esse código será apresentado em uma seção posterior).

O código de exemplo a seguir mostra uma implementação para o GetContentID:

	HRESULT CLicenseExpirationChecker::GetContentID(BSTR* pbstrContentID)
{
    // Check for initialization.
    ATLASSERT(m_spDRMEditor);
    ATLASSERT(pbstrContentID);
 
    HRESULT hr = S_OK;
    WMT_ATTR_DATATYPE DataType = WMT_TYPE_DWORD;
    WORD cbContentID = 0;
    WCHAR *pwszContentID = NULL;
    
    // Get the size needed for the attribute.
    hr = m_spDRMEditor->GetDRMProperty(g_wszWMDRM_DRMHeader_ContentID, 
                                       &DataType, 
                                       NULL, 
                                       &cbContentID);

    // Verify that the attribute is a string.
    if(SUCCEEDED(hr) && 
       (DataType != WMT_TYPE_STRING || cbContentID % sizeof(WCHAR) != 0))
    {
        hr = E_UNEXPECTED;
    }

    if(SUCCEEDED(hr))
    {
        // Allocate memory for the string.
        pwszContentID = new WCHAR[cbContentID / sizeof(WCHAR)];
        if(pwszContentID == NULL)
        {
            hr = E_OUTOFMEMORY;
        }
    }

    if(SUCCEEDED(hr))
    {
        // Get the attribute.
        hr = m_spDRMEditor->GetDRMProperty(g_wszWMDRM_DRMHeader_ContentID, 
                                           &DataType, 
                                           (BYTE*)pwszContentID, 
                                           &cbContentID);
    }
    
    if(SUCCEEDED(hr))
    {       
        // Allocate and return the BSTR.
        *pbstrContentID = SysAllocString(pwszContentID);
        if(NULL == pbstrContentID)
        {
            hr = E_OUTOFMEMORY;
        }
    }
   
    if(pwszContentID)
    {
        delete[] pwszContentID;
        pwszContentID = NULL;
    }

    return hr;
}

Gerenciando o Processo Background

Pode-se criar o processo background de maneira que se desejar. Nesse exemplo, apenas um processo background para atualização de licenças será executado por vez e todo o processo irá acontecer uma vez por sessão. Isso significa que o processo será criado apenas se o processo ainda não existe. Se o processo existe, será sinalizado um evento para pausar o processo quando o Windows Media Player 10 chamar IWMPSubscriptionService2::stopBackgroundProcessing e sinalizar o reinício quando o Player chamar IWMPSubscriptionService::startBackgroundProcessing. Também será usado um evento para forçar o processo a sair quando o plug-in for destruído.

O código de exemplo a seguir declara variáveis para armazenar os tratadores (handlers) de eventos.

Também é declarada uma variável para armazenar o tratador do processo. Essas variáveis devem ser declaradas como membros de sua classe plug-in e lembre-se de inicializá-las com NULL no construtor da classe.

public:
// Event handles
HANDLE m_hExitThreadsEvent;
HANDLE m_hPauseThreadsEvent;
private:
// Thread handle
HANDLE m_hLicenseUpdateThread;

O código a seguir cria os objetos de eventos. Pode-se incluir este código na implementação de FinalConstruct do seu plug-in. Esses eventos também serão usados para gerenciar outros processos criados mais adiante neste exemplo.

if(SUCCEEDED(hr))
{
    // Create an event to signal running background
    // threads to exit.
    m_hExitThreadsEvent = CreateEvent(NULL, TRUE, FALSE, NULL);

    if(0 == m_hExitThreadsEvent)
    {
        hr = HRESULT_FROM_WIN32(GetLastError());
    }
}

if(SUCCEEDED(hr))
{
    // Create an event to signal running background
    // threads to pause.
    m_hPauseThreadsEvent = CreateEvent(NULL, TRUE, FALSE, NULL);

    if(0 == m_hPauseThreadsEvent)
    {
        hr = HRESULT_FROM_WIN32(GetLastError());
    }
}

É uma boa idéia finalizar explicitamente esses tratadores porque o plug-in não é executado no seu próprio processo. Isso deve ser feito como parte de seu código de limpeza na sua implementação de FinalRelease.

Para garantir que o processo background ocorra uma vez por sessão, será necessário criar um marcador global que indique quando o processo foi executado. Deve-se declarar a seguinte variável como um membro privado de sua classe plug-in.

// Flag to only run license update thread once per session.
BOOL m_bRanLicenseUpdate;

Lembre-se de inicializar o marcador como FALSE no seu construtor.

A seguir, segue a declaração do procedimento para o processo background:

// Thread function for background license updating.
DWORD WINAPI LicenseUpdateThread(void *pArg);

O código de exemplo a seguir mostra uma implementação para startBackgroundProcessing. Este método contém a lógica para criar ou reiniciar o processo background.

HRESULT CProsewarePlugin::startBackgroundProcessing(HWND hwnd)
{
    // This example code runs a background thread once
    // per session. You might choose to perform this operation 
    // less frequently.

    HRESULT hr = S_OK;
 
    // Check whether the background thread is live
    if(m_hLicenseUpdateThread)
    {
        DWORD dwExitCode = 0;
        if(GetExitCodeThread(m_hLicenseUpdateThread, &dwExitCode))
        {
            if(STILL_ACTIVE == dwExitCode)
            {                  
                // Signal the event to restart the 
                // license background thread.
                ResetEvent(m_hPauseThreadsEvent);
            }
        }
    }
    else if(!m_bRanLicenseUpdate)
    {
        m_hLicenseUpdateThread = CreateThread(NULL, 0,
                                    LicenseUpdateThread, 
                                    (LPVOID)this, NULL, 0);

        if(NULL == m_hLicenseUpdateThread)
        {
            hr = HRESULT_FROM_WIN32(GetLastError());
        }
        else
        {
            // Set the flag.
            m_bRanLicenseUpdate = TRUE;
        }
    }    

    return hr;
}

O código a seguir mostra uma implementação para stopBackgroundProcessing. Este método contém a lógica para pausar o processo background.

HRESULT CProsewarePlugin::stopBackgroundProcessing(void)
{
    // Check whether the background thread is live
    if(m_hLicenseUpdateThread)
    {
        DWORD dwExitCode = 0;
        if(GetExitCodeThread(m_hLicenseUpdateThread, &dwExitCode))
        {
            if(STILL_ACTIVE == dwExitCode)
            {
                // Signal the event to pause the 
                // license background thread
                SetEvent(m_hPauseThreadsEvent);
            }
        }
    }
   
    return S_OK;
}

Usando o Plug-in para Requisitar Pré-distribuição de Licença

Pode-se criar uma classe auxiliar para gerenciar a pré-distribuição de licença no seu plug-in. O código de exemplo a seguir mostra essa classe, incluindo implementação inline. O método denominado Init simplesmente cria o objeto RMGetLicense. O método preDeliverLicense requisita a pré-distribuição de licença.

#include <msnetobj.h> // Header required for RMGetLicense object.
const WCHAR kszPredeliveryURL[] = 
    L"https://www.proseware.com/MusicStoreLicense.asp";

class CLicenseDelivery
{
private:
    CComPtr<IRMGetLicense> m_spGetLicense;

public:
    HRESULT Init()
    {
        return m_spGetLicense.CoCreateInstance(
            __uuidof(RMGetLicense), 
            0,
            CLSCTX_INPROC_SERVER);
    }

    
    HRESULT preDeliverLicense(BSTR bstrCID, 
                              BSTR bstrUID, 
                              bool bNeedsCopies)
    {
        if(!m_spGetLicense)
        {
            return NS_E_INVALID_REQUEST;
        }

        if(NULL == bstrCID)
        {
            return E_INVALIDARG;
        }

        HRESULT hr = S_OK;

        CComBSTR bstrURL;
        hr = bstrURL.Append(kszPredeliveryURL);

        if(SUCCEEDED(hr))
        {
            hr = bstrURL.Append(L"?cid=");
        }

        if(SUCCEEDED(hr))
        {
            hr = bstrURL.AppendBSTR(bstrCID);
        }

        if(SUCCEEDED(hr))
        {
            hr = bstrURL.Append(L"&uid=");
        }

        if(SUCCEEDED(hr))
        {
            hr = bstrURL.AppendBSTR(bstrUID);
        }

        if(SUCCEEDED(hr))
        {
            hr = bstrURL.Append(L"&NeedsCopies=");
        }

        if(SUCCEEDED(hr))
        {
            if(true == bNeedsCopies)
            {
                hr = bstrURL.Append(L"true");
            }
            else
            {
                hr = bstrURL.Append(L"false");
            }
        }

        if(SUCCEEDED(hr))
        {
            hr = m_spGetLicense->GetLicenseFromURL(CComBSTR(NULL), bstrURL);
        }

        return hr;
    }

};

Escrevendo o Código do Processo Background

O funcionamento do processo background agrupa todos as demais funcionalidades. Os objetos Windows Media Player 10 permitem acessar os conteúdos distribuídos por sua loja no computador do usuário e determinar o caminho dos arquivos. A classe de checagem de expiração de licença ajuda a determinar que licenças requerer. A classe de distribuição de licença permite requisitar a pré-distribuição de licenças raiz e folhas.

Do ponto de vista do Format SDK, é importante entender que não há o conceito de licenças raiz e folha; há apenas o direito de reproduzir ou não uma mídia. Se não houver direito de reprodução por causa de expiração de licença, o Windows Media Format SDK pode transportar essa informação usando a enumeração DRM_LICENSE_STATE_CATEGORY. Entretanto, quando o valor retornado é apenas simplesmente WM_DRM_LICENSE_STATE_NORIGHT, não se pode saber se é porque a licença folha, a licença raiz ou ambas são inválidas. Isso significa que quando a classe de checagem de expiração retorna um valor ID de conteúdo, deve-se primeiro tentar recuperar a licença raiz e então tentar recuperar a licença folha para o mesmo item de conteúdo.

O seguinte código de exemplo mostra uma implementação da função de atualização de licença em background. Os comentários podem ajudar a entender o processo. Observe as chamadas a WaitForSingleObject no final do bloco de laço para entender como o processo pausa, reinicia e sai em resposta a eventos levantados na classe plug-in.

DWORD WINAPI LicenseUpdateThread(void *pArg)
{ 
    // Do parameter validation.
    if(!pArg)
    {
        SetLastError(E_INVALIDARG);
        return 1;
    }

    // Threads must always call CoInitialize.
    CoInitialize(NULL);

    // Declare and initialize variables.
    HRESULT hr = S_OK;
    CComBSTR bstrUID(kszUserID);
    CComPtr<IWMPCore> spPlayer;  // Smart pointer to IWMPCore interface.
    CComPtr<IWMPMediaCollection> spMediaCollection;
    CComPtr<IWMPPlaylist> spPlaylist;
    CProsewarePlugin *pPlugin = (CProsewarePlugin*)pArg;
    long lCount = 0;
    bool bGotRoot = false; // Flag to store whether the root
                          // license has been acquired.
    CLicenseDelivery *pLicenseDelivery = NULL;
    CLicenseExpirationChecker *pLicExpChk = NULL;
    // Variables to query for our content from the library.
    CComBSTR bstrAttName;  // "WM/ContentDistributor"
    CComBSTR bstrAttVal;  // "Proseware"

    hr = bstrAttName.Append(L"WM/ContentDistributor");

    if(SUCCEEDED(hr))
    {
        hr = bstrAttVal.Append(L"Proseware");
    }

    if(SUCCEEDED(hr))
    {
        // Create an instance of the license expiration checker class.
        pLicExpChk = new CLicenseExpirationChecker();
        if(NULL == pLicExpChk)
        {
            hr = E_OUTOFMEMORY;
        }
    }

    if(SUCCEEDED(hr))
    {
        // Create an instance of the license delivery class.
        pLicenseDelivery = new CLicenseDelivery;

        if(NULL == pLicenseDelivery)
        {
            hr = E_OUTOFMEMORY;
        }
    }

    if(SUCCEEDED(hr))
    {
        // Initialize license expiration checker.
        hr = pLicExpChk->Initialize();
    }

    ATLASSERT(pPlugin);

    if(SUCCEEDED(hr))
    {
        // Create an instance of the Windows Media Player 10
        // object. We do this here because the plug-in does not
        // have a pointer to IWMPCore and creating the object in
        // the worker thread avoids the need to marshal COM pointers
        // across apartment boundaries.
        hr = spPlayer.CoCreateInstance(__uuidof(WindowsMediaPlayer),
                                        0, CLSCTX_INPROC_SERVER);
    }

    if(SUCCEEDED(hr))
    {
        // Retrieve a pointer to the media collection.
        hr = spPlayer->get_mediaCollection(&spMediaCollection);
    }

    if(SUCCEEDED(hr))
    {
        // Retrieve a playlist filled with online store media
        // from the user's library.
        hr = spMediaCollection->getByAttribute(bstrAttName, 
                                               bstrAttVal,
                                               &spPlaylist);
    }

    if(SUCCEEDED(hr))
    {
       hr = spPlaylist->get_count(&lCount);
    }

    if(SUCCEEDED(hr))
    {
        // Initialize the license delivery class
        hr = pLicenseDelivery->Init();
    }

    if(SUCCEEDED(hr))
    {   
        // We need to cache the last contentID so we can 
        // handle the case where
        // we have the leaf license, but the root is missing
        // or corrupted.
        CComBSTR bstrLastCID; 

        // Loop through the playlist.
        for(long i = 0; i < lCount; i++)
        {            
            CComPtr<IWMPMedia> spMedia;
            CComBSTR bstrURL;
            CComBSTR bstrCID;
            // Retrieve a pointer to the next media item.
            hr = spPlaylist->get_item(i, &spMedia);

            if(SUCCEEDED(hr))
            {                
                // Retrieve the path to the media item.
                hr = spMedia->get_sourceURL(&bstrURL);
            }

            if(SUCCEEDED(hr) && bstrURL.Length())
            {
                // Check the license for expiration within a week.
                hr = pLicExpChk->CheckExpiration(bstrURL, 7, &bstrCID); 
            }

            // Test whether the license expiration checker is requesting 
            // a root license and the root has already been retrieved.
            if(SUCCEEDED(hr) && bstrCID.Length() &&
               0 == _wcsicmp(bstrCID, L"root") &&
               true == bGotRoot)
            {
                // Cache the CID
                bstrLastCID.Empty();
                hr = bstrLastCID.AppendBSTR(bstrCID); 

                // Don't do any license acquisition 
                // in this iteration.
                bstrCID.Empty();
            }

            if(SUCCEEDED(hr) && bstrCID.Length())
            { 
                // Test whether this CID is a repeat of the last.
                // If not, acquire a root instead of a leaf.
                // Note: The "!=" operator here is the overloaded one
                // from the CComBSTR class. It compares strings,
                // not pointers.
                if(bstrCID != bstrLastCID &&
                   false == bGotRoot &&
                   0 != _wcsicmp(bstrCID, L"root"))
                {
                    // Cache the CID
                    bstrLastCID.Empty();
                    hr = bstrLastCID.AppendBSTR(bstrCID);
                    if(SUCCEEDED(hr))
                    {
                        bstrCID.Empty();
                        bstrCID = L"root";
                    }
                }
                else
                {
                     bstrLastCID.Empty();
                     hr = bstrLastCID.AppendBSTR(bstrCID);
                }

                if(SUCCEEDED(hr))
                {
                    // The usual scenario is that bstrCID equals "root" 
                    // because the root is expired or missing.
                    // This call would then get the root license.
                    // It's possible bstrCID contains a content ID.
                    // In that case, this call would get the leaf license.
                    hr = pLicenseDelivery->preDeliverLicense(bstrCID, 
                                                             bstrUID, 
                                                             false);
                 }

                if(SUCCEEDED(hr) && 0 == _wcsicmp(bstrCID, L"root"))
                {
                    // Check this item
                    // again to ensure we have a leaf.
                    i--;

                    // Set the flag.
                    bGotRoot = true;
                }
            }

            // Sleep if the pause event is signaled.          
            while(WAIT_OBJECT_0 == WaitForSingleObject(
                    pPlugin->m_hPauseThreadsEvent, 1000))
            {
                // Keep waiting for the event to be non-signaled.
            }

            // Exit if the exit event is signaled.          
            if(WAIT_OBJECT_0 == WaitForSingleObject(
                   pPlugin->m_hExitThreadsEvent, 0))
            {
                break;
            }
        }
    }

    if(pLicExpChk)
    {
        delete pLicExpChk;
        pLicExpChk = NULL;
    }

    if(pLicenseDelivery)
    {
        delete pLicenseDelivery;
        pLicenseDelivery = NULL;
    }

    CoUninitialize();

    return SUCCEEDED(hr)?0:1;
}

Saindo do Processo

Quando o processo background completa seu trabalho, ele será finalizado com o retorno do procedimento. Entretanto, o plug-in pode ser destruído antes que isso aconteça. Por exemplo, o usuário pode fechar o Windows Media Player 10. O plug-in deve tratar esse caso sinalizando explicitamente o processo para sair quando o plug-in fechar. Pode-se adicionar código como o exemplo a seguir na sua implementação de FinalRelesase.

// Signal threads to exit.
SetEvent(m_hExitThreadsEvent);
// Wake up sleeping background threads.
ResetEvent(m_hPauseThreadsEvent);

HANDLE hThreads[1] = {m_hLicenseUpdateThread};
// Wait for the threads to exit.
WaitForMultipleObjects(1, hThreads, TRUE, 15000);

Esse código exemplo será modificado em uma seção adiante para forçar a finalização de processos adicionais que forem criados.

Preparando Licenças para Sincronização

Pode-se escrever código no cliente que inspecione as licenças associadas aos conteúdos que o Windows Media Player 10 esteja transferindo para um dispositivo portátil. Pode-se querer fazer isso para garantir que conteúdos de uma assinatura sendo transferidos não estejam para expirar. Para isso funcionar, será necessário escrever código C++ no plug-in Windows Media Player 10 de sua loja on-line.

O Windows Media Player 10 chama IWMPSubscriptionService2::prepareForSync logo antes que a sincronização aconteça. Os parâmetros para a chamada provêem um BSTR contendo o caminho para o arquivo de mídia digital a ser transferido, um BSTR contendo o nome canônico para o dispositivo sendo sincronizado e um ponteiro IWMPSubscriptionServiceCallback, que deve ser usado para sinalizar o Windows Media Player que o processamento do arquivo foi finalizado. Parte do processo de sincronização do Player inclui sincronizar licenças entre aquelas armazenadas no computador e aquelas armazenadas no dispositivo portátil, caso a licença no dispositivo esteja próximo de expirar. A estratégia neste exemplo é simplesmente garantir que a licença no computador estará atualizada antes que a sincronização aconteça. Desse modo, pode-se evitar a necessidade de trabalhar diretamente com as licenças no dispositivo. Isso significa que a informação de nome canônico do dispositivo não será usada.

Para evitar bloquear o Windows Media Player 10, é importante que o trabalho de inspecionar e atualizar licenças neste cenário seja feita por uma worker thread (processo de trabalho). Essa thread deve conter um laço de mensagem Windows. Uma vez que o Player pode chamar prepareForSync múltiplas vezes durante a mesma operação de sincronização, deve-se postar uma mensagem Windows específica para o processo cada vez que o método é chamado. Essa técnica permite o uso da fila de mensagem para serializar as chamadas para a worker thread, o que significa que o processo aciona um arquivo de mídia digital por vez. O processo funciona como a seguir:

  1. O Windows Media Player 10 chama prepareForSync. Aqui, o plug-in de loja on-line cria e preenche uma estrutura que contém informações que a worker thread usa para inspecionar e atualizar a licença do arquivo de mídia digital especificado. A estrutura inclui o ponteiro IWMPSubscriptionServiceCallback que o Player provê;

  2. O plug-in posta uma mensagem Windows para a fila de mensagens do processo de trabalho. A mensagem permanece lá até que o processo a remova para processamento. Nesse ponto, o plug-in retorna do prepareForSync. A mensagem contém um ponteiro para a estrutura criada no passo 1;

  3. Eventualmente, o laço de mensagem do processo aciona a mensagem Windows e acessa informações na estrutura para realizar o trabalho de licenciamento;

  4. O processo inspeciona e atualiza as licenças, caso necessário;

  5. Quando o trabalho é completado, o processo posta uma mensagem Windows diferente para uma janela criada pelo plug-in. A mensagem contém o ponteiro IWMPSubscriptionServiceCallback;

  6. O plug-in usa o ponteiro de callback para sinalizar ao Windows Media Player 10 que o trabalho está completado para o arquivo de mídia digital.

O diagrama a seguir mostra como o processo funciona.

ms867139.WMP10_03(pt-br,MSDN.10).jpg

As próximas seções provêem mais detalhes.

Variáveis Membros

O código de exemplo a seguir declara variáveis membros que serão necessárias neste cenário. Elas devem ser declaradas como membro de sua classe plug-in e lembre-se de inicializá-las com NULL no seu construtor de classe.

private:
UINT  m_uMarshalCustom; // Window message to complete the callbacks.
HWND  m_hPluginWindow; // Handle of the plug-in's hidden window.
HANDLE  m_hPrepareForSyncThread; // Thread handle
UINT  m_uExitPrepareForSync; // Window message to exit the thread.
DWORD  m_dwPrepareForSyncThreadId; // Thread ID.
UINT  m_uPrepareMediaSync; // Window message for license work.

Sobre a Janela do Plug-in

O seu plug-in de loja on-line deve criar uma janela oculta. Essa janela será usada para garantir que chamadas de callback feitas para o Windows Media Player 10 aconteçam no mesmo processo no qual o ponteiro de callback foi fornecido. Enviar mensagens de sua worker thread para essa janela (que é criada no processo principal de seu plug-in) é uma forma conveniente de implementar isso.

Seu código deve criar a janela oculta na sua implementação de FinalConstruct e destruí-la na sua implementação de FinalRelease. Como a janela será criada depende de você. O importante é que o tratador da janela esteja disponível como um membro de sua classe plug-in.

Sobre Mensagens Windows Específicas

Serão necessárias três mensagens Windows para este exemplo: uma para postar um item para processamento na worker thread, outra para enviar callbacks da worker thread para o processo principal, e uma terceira para finalizar a worker thread. Deve-se criar essas mensagens chamando a função Windows RegisterWindowMessage, informando um nome único para cada mensagem. Essa função garante uma única nova mensagem no sistema a primeira vez que for chamada. Chamadas subseqüentes usando a mesma string de mensagem retornarão o mesmo valor de mensagem que a chamada inicial.

O código de exemplo a seguir mostra três chamadas feitas em FinalConstruct para registrar as três mensagens usadas pela classe plug-in.

m_uMarshalCustom = RegisterWindowMessage(_T("ProsewarePlugin_CB"));
if(0 == m_uMarshalCustom)
{
    hr = HRESULT_FROM_WIN32(GetLastError());
}

if(SUCCEEDED(hr))
{
    m_uExitPrepareForSync = RegisterWindowMessage(
        _T("ProsewarePlugin_ExitPrepareForSync"));
    if(0 == m_uExitPrepareForSync)
    {
        hr = HRESULT_FROM_WIN32(GetLastError());
    }
}

if(SUCCEEDED(hr))
{
    m_uPrepareMediaSync = RegisterWindowMessage(
        _T("ProsewarePlugin_PrepareMediaSync"));
    if(0 == m_uPrepareMediaSync)
    {
        hr = HRESULT_FROM_WIN32(GetLastError());
    }
}

Tenha em mente que será necessário realizar chamadas similares em outros contextos. Por exemplo, sua janela do plug-in precisará tratar a mensagem ProsewarePlugin_CB, então será preciso chamar RegisterWindowMessage no seu código usando essa mesma string.

Gerenciando a Worker Thread (Processo de Trabalho)

Este exemplo usa a seguinte função específica:

STDMETHODIMP prepareMedia(BSTR bstrFile,
                   IWMPSubscriptionServiceCallback *pCB, 
                   HWND hWndCB);

A função prepareMedia cria a worker thread, se necessário, e posta-lhe a mensagem para adicionar um novo arquivo de mídia digital para processamento de licença. Deve-se chamar prepareMedia na sua implementação de prepareForSync. O parâmetro bstrFile é o nome de arquivo informado pelo Windows Media Player 10, o parâmetro pCB é o ponteiro de callback informado pelo Windows Media Player 10, e o parâmetro hWndCB é o tratador (handler) da janela oculta do plug-in.

Este exemplo usa a seguinte estrutura para transmitir dados do plug-in para a worker thread:

struct ThreadParams{
    BSTR bstrName; // Canonical name of device or file name
    void *pCB; // Callback pointer
    HWND hWndCB; // HWND to send callback pointer to
    HANDLE hInit; // Event handle to signal thread has started
    HRESULT hr; // Return code from thread initialization
};

Para este cenário, o membro bstrName corresponde ao nome do arquivo. A implementação de exemplo de prepareMedia cria uma nova instância da estrutura ThreadParams, preenche os seus membros e posta uma mensagem contendo um ponteiro para a estrutura para a worker thread. A worker thread libera essa memória quando o processamento for concluído.

O plug-in não deve postar mensagens para o processo até que ele tenha finalizado a inicialização. O membro hInit provê um tratador para um objeto de evento que o processo pode marcar para sinalizar quando estiver pronto para receber mensagens. O código que cria o processo espera este evento ser sinalizado antes de continuar com mais processamento. Pode-se ver isso na implementação de exemplo a seguir da função prepareMedia.

HRESULT CProsewarePlugin::prepareMedia(
    BSTR bstrFile, 
    IWMPSubscriptionServiceCallback *pCB,
    HWND hWndCB)
{
    if(NULL == hWndCB ||
       NULL == pCB)
    {
        return E_INVALIDARG;
    }

    HRESULT hr = S_OK;
    BOOL bResult = FALSE;

    CComBSTR bstrFileName;
    bstrFileName.Append(bstrFile);    

    // Create the thread if none exists.
    if(!m_hPrepareForSyncThread)
    {   
        ThreadParams *pTP = NULL;
        HANDLE hEvent = ::CreateEvent(NULL, TRUE, FALSE, NULL);

        if(NULL == hEvent)
        {
            hr = HRESULT_FROM_WIN32(GetLastError());
        }

        if(SUCCEEDED(hr))
        {
            pTP = new ThreadParams();
            if(!pTP)
            {
                hr = E_OUTOFMEMORY;
            }
        }

        if(SUCCEEDED(hr))
        {
            ZeroMemory(pTP, sizeof(ThreadParams));
            pTP->hInit = hEvent;

            // Create the thread.
            // There should only be one thread of this type per session.
            m_hPrepareForSyncThread = CreateThread(
                NULL, 
                0,
                PrepareForSyncThread, 
                (void*)pTP, NULL, 
                &m_dwPrepareForSyncThreadId);

            if(m_hPrepareForSyncThread)
            {
                // Wait for the thread to finish initialization.
                if(WAIT_OBJECT_0 != WaitForSingleObject(
                       pTP->hInit, 30000))
                {
                    // Failed to create thread
                    CloseHandle(m_hPrepareForSyncThread);
                    m_hPrepareForSyncThread = NULL;
                    hr = pTP->hr;
                }

                // Clean up.
                CloseHandle(pTP->hInit);
                pTP->hInit = NULL;
                delete pTP;
                pTP = NULL;
            }
        }
    }

    // If we have a worker thread, fill in the ThreadParams struct
    // and post a message.
    if(m_hPrepareForSyncThread)
    {
        // Prepare the message
        ThreadParams *pThreadParams = new ThreadParams();
        if(!pThreadParams)
        {
            hr = E_OUTOFMEMORY;
        }

        if(SUCCEEDED(hr))
        {
            // Increment the reference count on the
            // callback pointer.
            pCB->AddRef();

            // Fill in the ThreadParams struct.
            ZeroMemory(pThreadParams, sizeof(ThreadParams));
            bstrFileName.CopyTo(&pThreadParams->bstrName);
            pThreadParams->hWndCB = hWndCB;
            pThreadParams->pCB = reinterpret_cast<void*>(pCB);

            if(m_dwPrepareForSyncThreadId)
            {
                // Send the struct pointer to the thread's message queue.
                bResult = PostThreadMessage(m_dwPrepareForSyncThreadId, 
                                            m_uPrepareMediaSync, 0, 
                                            (LPARAM)pThreadParams);
            }

            if(FALSE == bResult)
            {
                hr = HRESULT_FROM_WIN32(GetLastError());
            }
        }
    }

    return hr;
}

O código de exemplo a seguir mostra a declaração do procedimento para a worker thread.

// Thread function for prepare for sync.
DWORD WINAPI PrepareForSyncThread(void *pArg);

Quando a worker thread começa, são realizados os seguintes passos:

  1. Cria-se uma instância da classe auxiliar CLicenseDelivery descrita em seções anteriores;

  2. Cria-se uma instância da classe auxiliar CLicenseExpirationChecker descrita em seções anteriores;

  3. Registra-se duas das mensagens Windows;

  4. Especifica-se um código de retorno e marca-se o evento para sinalizar o plug-in que o processo finalizou a inicialização;

  5. Começa o laço de mensagens.

O laço de mensagens apenas processa duas das mensagens Windows criadas neste exemplo. Uma é a mensagem para preparar o arquivo de mídia digital para sincronização. A outra é a mensagem que sinaliza o processo a finalizar-se. Qualquer outra mensagem é simplesmente descartada.

Quando o processo recebe uma mensagem Windows para preparar um arquivo para sincronização, ele chama uma função específica para realizar o trabalho.

Quando o processo recebe a mensagem para sair, o código limpa a fila de mensagens. Para cada mensagem na fila, é postada uma mensagem de callback contendo um código de falha para a janela oculta do plug-in. Isso sinaliza o Windows Media Player 10 que o processamento não foi concluído para os arquivos de mídia digital associados.

O código de exemplo a seguir mostra uma implementação do procedimento.

DWORD WINAPI PrepareForSyncThread(void *pArg)
{
    // Parameter checking.
    if(NULL == pArg)
    {
        SetLastError(E_INVALIDARG);
        return 1;
    }

    CoInitialize(NULL);

    MSG msg; 
    ZeroMemory(&msg, sizeof(MSG));
    HRESULT hr = S_OK;
    CLicenseExpirationChecker *pLicExpChk = NULL;
    ThreadParams *pTP = reinterpret_cast<ThreadParams*>(pArg);
    UINT uExitPrepareForSync = 0;
    UINT uPrepareMediaSync = 0;

    // Create an instance of the license deliver class.
    CLicenseDelivery *pLicenseDelivery = new CLicenseDelivery();
    if(NULL == pLicenseDelivery)
    {
        hr = E_OUTOFMEMORY;
    }

    if(SUCCEEDED(hr))
    {
        hr = pLicenseDelivery->Init();
    }

    if(SUCCEEDED(hr))
    {
        // Create an instance of the license expiration checker class.
        pLicExpChk = new CLicenseExpirationChecker();
        if(NULL == pLicExpChk)
        {
            hr = E_OUTOFMEMORY;
        }
    }

    if(SUCCEEDED(hr))
    {
        hr = pLicExpChk->Initialize();
    }

    if(SUCCEEDED(hr))
    {
        uExitPrepareForSync = RegisterWindowMessage(
                 _T("ProsewarePlugin_ExitPrepareForSync"));
        uPrepareMediaSync = RegisterWindowMessage(
                 _T("ProsewarePlugin_PrepareMediaSync"));
    }

    // Set the return code.
    pTP->hr = hr;
    // Signal that we're ready to pump messages. 
    SetEvent(pTP->hInit);

    if(SUCCEEDED(hr))
    {        
        BOOL bRet = FALSE;

        while((bRet = ::GetMessage(&msg, 0, 0, 0)) != 0)
        {
            if(-1 == bRet)
            {
                hr = HRESULT_FROM_WIN32(GetLastError());
                break;
            }

            if(uPrepareMediaSync == msg.message)
            {
                if(msg.lParam != NULL)
                {
                    PrepareMediaForSync(
                        pLicenseDelivery, 
                        pLicExpChk,
                        (LPVOID)msg.lParam);
                }
            }
            else if(uExitPrepareForSync == msg.message)
            {
                UINT uMarshalCustom = RegisterWindowMessage(
                                _T("ProsewarePlugin_CB"));
                if(NULL == uMarshalCustom)
                {
                    hr = HRESULT_FROM_WIN32(GetLastError());
                }

                if(SUCCEEDED(hr))
                {
                    // Empty the message queue.
                    while(0 == PeekMessage(
                                 &msg, 
                                 (HWND)INVALID_HANDLE_VALUE, 
                                 0, 0, PM_REMOVE))
                    {
                        if(uPrepareMediaSync == msg.message)
                        {                            
                            ThreadParams *pPrep = 
                              reinterpret_cast<ThreadParams*>(msg.lParam);
                            ATLASSERT(pPrep);

                            HWND hwndCB = pPrep->hWndCB;
                            void *pCB = pPrep->pCB;
                            if(hwndCB && pCB)
                            {
                                // Do the callback
                                PostMessage(hwndCB,
                                            uMarshalCustom,
                                            (WPARAM)NS_E_USER_STOP,
                                            (LPARAM)pCB);
                            }
                           
                            SysFreeString(pPrep->bstrName);
                            delete pPrep;
                            pPrep = NULL;
                        }
                        else
                        {
                            DispatchMessage(&msg);
                        }
                    }
                }
                break;
            }
            else
            {
                DispatchMessage(&msg);
            }
        }
    }
    
    if(pLicExpChk)
    {
        delete pLicExpChk;
        pLicExpChk = NULL;
    }

    CoUninitialize();
 
    return SUCCEEDED(hr)?0:1;
}

Preparando Mídias Digitais para Sincronização

Quando o laço de mensagens na worker thread recebe uma mensagem para preparar um arquivo de mídia digital para sincronização, é chamada a função a seguir.

void PrepareMediaForSync(CLicenseDelivery *pLicenseDelivery,
                         CLicenseExpirationChecker *pLicExpChk,
                         void* pv)

A função PrepareMediaForSync realiza o trabalho de inspeção e atualização das licenças. O parâmetro pLicenseDelivery é um ponteiro para a classe CLicenseDelivery criada pela worker thread. O parâmetro pLicExpChk é um ponteiro para a classe CLicenseExpirationChecker criada pela worker thread. O parâmetro pv aponta para a estrutura ThreadParams contida na mensagem Windows.

O trabalho realizado pela função é similar àquele realizado pelo processo de atualização de licenças em background. É usada a classe CLicenseExpirationChecker para determinar se a licença precisa ser atualizada. Isso acontece duas vezes, pelas mesmas razões descritas na seção anterior. O código também chama uma nova função que deve ser adicionada à classe CLicenseExpirationChecker. Essa função, chamada CheckCopyCount, inspeciona a licença para determinar se ela possui direitos restantes de cópia. Tenha em mente que o contador de cópias se refere à contagem de licenças transferidas com sucesso para dispositivos, não o número de vezes que o usuário tentou transferir o conteúdo.

A assinatura e uso da função CheckCopyCount é similar à assinatura da função CheckExpiration. O código de exemplo a seguir mostra uma implementação para CheckCopyCount.

HRESULT CLicenseExpirationChecker::CheckCopyCount(
    BSTR  bstrFileName,
    DWORD dwCountWindow,
    BSTR* pbstrUpdateContentID)
{
    // Check for initialization.
    if(!m_spDRMEditor)
    {
        return NS_E_INVALID_REQUEST;
    }

    // Parameter checking.
    if(NULL == pbstrUpdateContentID ||
       NULL == bstrFileName ||
       0 == SysStringLen(bstrFileName))
    {
        return E_INVALIDARG;
    }

    ///// Local variables
    HRESULT hr = S_OK;

    // Variables for retrieving the license state information.
    WM_LICENSE_STATE_DATA*  pWMLicState  = NULL;

    WMT_ATTR_DATATYPE       DataType = WMT_TYPE_DWORD;       
    BYTE* pbData = NULL;
    WORD  cbData = 0;
    BOOL bProtected = FALSE;

    // Make sure the content is DRM protected.
    hr = WMIsContentProtected((WCHAR*)bstrFileName, &bProtected);

    if(SUCCEEDED(hr))
    {
        if(FALSE == bProtected)
        {
            hr = NS_E_INVALID_REQUEST;
        }
    }

    if(SUCCEEDED(hr))
    {
        // Open the file for metadata editing.
        hr = m_spEditor->Open((WCHAR*)bstrFileName);
    }

    if(SUCCEEDED(hr))
    {
        // Get the license state data.

        // First get the size of the data.
        hr = m_spDRMEditor->GetDRMProperty(g_wszWMDRM_LicenseState_Copy,
                                           &DataType,
                                           NULL,
                                           &cbData);
    }

    // License state data attributes should always be a binary data type.
    if(SUCCEEDED(hr) && DataType != WMT_TYPE_BINARY)
    { 
        hr = E_UNEXPECTED;
    }

    if(SUCCEEDED(hr))
    {
        // Allocate memory for the data.
        pbData = new BYTE[cbData];
        if(pbData == NULL)
        {
            hr = E_OUTOFMEMORY;
        }
    }

    if(SUCCEEDED(hr))
    {
        // Get the actual data.
        hr = m_spDRMEditor->GetDRMProperty(g_wszWMDRM_LicenseState_Copy,
                                           &DataType,
                                           pbData,
                                           &cbData);
    }

    if(SUCCEEDED(hr))
    {
        // Set the license state data pointer.
        pWMLicState = (WM_LICENSE_STATE_DATA*)pbData;

        switch(pWMLicState->stateData->dwCategory)
        {
        // If the state category is NORIGHT, then either the 
        // leaf license or the root license needs to be acquired.
        // In this case, set the string to the content ID.
        case WM_DRM_LICENSE_STATE_NORIGHT:
            {
                hr = GetContentID(pbstrUpdateContentID);
                break;
            }
        case WM_DRM_LICENSE_STATE_COUNT:
        case WM_DRM_LICENSE_STATE_COUNT_FROM:
        case WM_DRM_LICENSE_STATE_COUNT_UNTIL:
        case WM_DRM_LICENSE_STATE_COUNT_FROM_UNTIL:
            {
                // Compare the count with the specified window.
                if(pWMLicState->stateData->dwCount[0] < dwCountWindow)
                {
                    hr = GetContentID(pbstrUpdateContentID);
                }

                break;
            }
        default:
            {
                hr = E_UNEXPECTED;
                break;
            }
        }        
    }
    
    m_spEditor->Close();

    if(pbData)
    {
        delete[] pbData;
        pbData = NULL;
    }

    pWMLicState = NULL;
    return hr;
}

O código de exemplo a seguir mostra uma implementação da função PrepareMediaForSync. Observe que o terceiro parâmetro de CLicenseDelivery::preDeliverlicense é marcado para verdadeiro quando o contador de cópia chegar a zero. Esse é o parâmetro NeedsCopies. Neste caso, o servidor de licenças precisa emitir uma licença de cópia adicional. Deve-se lembrar que as regras de negócio enumeradas no início deste artigo especificam esse comportamento.

void PrepareMediaForSync(CLicenseDelivery *pLicenseDelivery,
                         CLicenseExpirationChecker *pLicExpChk,
                         void* pv)
{
    ATLASSERT(pLicenseDelivery);
    ATLASSERT(pLicExpChk);
    ATLASSERT(pv);
    
    HRESULT hr = S_OK;
    static bool bHaveRoot = false; // Only get the root once per session.
    ThreadParams *pPrep = reinterpret_cast<ThreadParams*>(pv);
    CComBSTR bstrFileName;
    bstrFileName.Attach(pPrep->bstrName);
    CComBSTR bstrUID;
    hr = bstrUID.Append(kszUserID);
    HWND hwndCB = pPrep->hWndCB;
    void *pCB = pPrep->pCB;
    CComBSTR bstrCID;

    // Free the ThreadParams struct.
    if(pPrep)
    {
        delete pPrep;
        pPrep = NULL;
    }

    hr = pLicExpChk->CheckExpiration(bstrFileName, 7, &bstrCID);

    if(SUCCEEDED(hr) && bstrCID.Length())
    {        
        // The normal scenario is that bstrCID == "root" when the root
        // is expired.
        // This code would then get the root license.
        // It's possible bstrCID contains a content ID. In that case,
        // This code would get the leaf license.
        hr = pLicenseDelivery->preDeliverLicense(bstrCID, bstrUID, false);

        if(SUCCEEDED(hr))
        {
            // Check a second time in case the root is invalid.
            // If the first pre-delivery got a leaf, we might still
            // need a root.
            bstrCID.Empty();
            hr = pLicExpChk->CheckExpiration(bstrFileName, 7, &bstrCID);
        }

        if(SUCCEEDED(hr) && 
           bstrCID.Length() &&
           false == bHaveRoot)
        {            
            // Retrieve a root license.
            hr = pLicenseDelivery->preDeliverLicense(CComBSTR("root"), 
                                                      bstrUID, false);

            if(SUCCEEDED(hr))
            { 
                bHaveRoot = true;
            }
        }
    }

    bstrCID.Empty();
    // Test whether this content has copy rights left.
    hr = pLicExpChk->CheckCopyCount(bstrFileName, 1, &bstrCID);

    if(SUCCEEDED(hr) && bstrCID.Length())
    {  
        // Set the third parameter to true to signal the 
        // server code that you're asking for a leaf because 
        // the copy count is 0.
        // This gives the server a chance to apply 
        // business rules to determine whether
        // the user should be granted additional copies.
        hr = pLicenseDelivery->preDeliverLicense(bstrCID, bstrUID, true);
    }
 
    // Post the callback message back to the main thread.
    if(hwndCB && pCB)
    {
        UINT uMarshalCustom = RegisterWindowMessage(
                               _T("ProsewarePlugin_CB"));

        if(uMarshalCustom)
        {
            // Post a message to the plug-in window to make 
            // the callback to Windows Media Player.
            PostMessage(hwndCB, uMarshalCustom, (WPARAM)hr, (LPARAM)pCB);
        }
    }
}

Realizando o Callback

A seção final do código de exemplo precedente posta uma mensagem Windows para ser tratada pela janela do plug-in. O Windows Media Player 10 requer a chamada de IWMPSubscriptionServiceCallback::onComplete usando o ponteiro que ele provê. Isso sinaliza ao Windows Media Player que o processamento foi completado para o arquivo de mídia digital associado ao ponteiro de callback. Se for retornado um código HRESULT de sucesso, o Windows Media Player procede com a transferência do arquivo para o dispositivo portátil; caso contrário, o Windows Media Player exibe uma mensagem de erro na sua interface com usuário. Postar uma mensagem para a janela do plug-in força essa chamada a acontecer no processo do plug-in ao invés da worker thread. Essa mesma técnica será usada novamente em uma seção adiante sobre medições.

(É interessante notar que ponteiros de interface COM usualmente devem ser preparados – marshaled – quando são passados entre processos. Quando o ponteiro de callback é passado para a worker thread, ele cruza uma fronteira de processos. Entretanto, uma vez que o ponteiro de callback nunca é usado pelo processo de trabalho, nenhuma preparação é necessária.)

O código de exemplo a seguir mostra uma implementação para uma função chamada OnMarshalCB. Este é um exemplo de função que sua janela pode chamar em resposta à mensagem recebida do ProsewarePlugin_CB.

LRESULT OnMarshalCB(UINT nMsg,
                    WPARAM wParam, 
                    LPARAM lParam, 
                    BOOL& bHandled)
{  
    CComPtr<IWMPSubscriptionServiceCallback> spCB;
    // Note that the smart pointer will call Release() on the 
    // callback pointer (the LPARAM) when out of scope.
    spCB.Attach(
        reinterpret_cast<IWMPSubscriptionServiceCallback*>(lParam));

    if(spCB)
    {
        spCB->onComplete((HRESULT)wParam); 
    }

    return 0;
}

Saindo do Processo

Deve-se agora modificar o código em FinalRelease para garantir que a worker thread sai quando o plug-in for finalizado. O código de exemplo a seguir modifica o código de exemplo de saída do processo mostrado na seção anterior. É adicionada uma chamada para PostThreadMessage para sinalizar a worker thread para sair e se adiciona seu tratador (handler) ao array de tratadores passados a WaitForMultipleObjects.

// Wake up sleeping background threads.
ResetEvent(m_hPauseThreadsEvent);
// Signal threads to exit.
SetEvent(m_hExitThreadsEvent);
PostThreadMessage(m_dwPrepareForSyncThreadId, 
                  m_uExitPrepareForSync,
                  0, 
                  0);

HANDLE hThreads[2] = {m_hLicenseUpdateThread, 
                      m_hPrepareForSyncThread};
// Wait for the threads to exit.
WaitForMultipleObjects(2, hThreads, TRUE, 15000);

Medindo o Uso de Conteúdos

Pode-se usar o seu plug-in de loja on-line para realizar funções de medições de uso. Para uma visão geral do funcionamento de medições e sua utilidade, consulte “Metering the Use of Digital Media Content with Windows Media DRM 10” na página da Microsoft (https://www.microsoft.com/windows/windowsmedia/howto/articles/metering_with_drm10.aspx).

Em geral, o processo de medição funciona da seguinte maneira:

  1. 1. O plug-in recupera dados da licença armazenada no computador do usuário ou no dispositivo móvel. Esses dados são chamados de convite de medições (metering challenge). Esse passo requer o Windows Media Device Manager 10 SDK;

  2. 2. Os dados são transmitidos usando HTTP para um serviço de agregação de medições. Esse serviço é implementado como uma página ASP em um servidor executando o Windows Server 2003. O serviço requer o Windows Media Rights Manager 10 SDK;

  3. 3. O serviço de agregação de medições usa o convite de medições para recuperar os dados de medições e armazená-los;

  4. 4. O serviço gera uma resposta de medição (metering response) e a transmite de volta ao computador do cliente (neste exemplo, o plug-in);

  5. 5. O plug-in usa a resposta de medição para reiniciar a licença armazenada. Esse passo é realizado usando o Windows Media Device Manager 10 SDK

Pode-se transmitir o convite de medição e receber sua resposta usando qualquer tecnologia desejada. Neste exemplo, é assumido que o convite de medição é enviado para a página ASP como uma operação HTML FORM POST. Isso significa que a página ASP recupera o convite como um par nome/valor. A página ASP retorna a resposta de medição chamando Response.Write.

É responsabilidade sua escrever o código que usa HTTP para transmitir o convite para uma página ASP e receber a resposta. O ponto chave a lembrar é que o convite e as strings de resposta devem permanecer inalterados depois de cada transmissão para que seu processamento funcione.

Criando a Página do Serviço de Agregação de Medições

A agregação de medições é realizada usando objetos do Windows Media Rights Manager 10 SDK em uma página ASP. O código de exemplo a seguir cria uma página ASP para o serviço de agregação de medições para a loja on-line baseada em assinaturas de exemplo.

<%@ LANGUAGE="VBScript"%>
<%
Response.Buffer = True
Response.Expires = 0
Do
    On Error Resume Next

    '"""""""""""""""""""""""""""""""""""""""""""""""""""""
    ' Declare variables
    '"""""""""""""""""""""""""""""""""""""""""""""""""""""
    Dim MObj                    ' WMRMMetering object
    Dim MDataObj                ' WMRMMeteringData object
    Dim MContentCollObj         ' WMRMMeteringContentCollection object
    Dim MContentObj             ' WMRMMeteringContent object
    Dim MActionCollObj          ' WMRMMeteringActionCollection object
    Dim MActionObj              ' WMRMMeteringAction object

    Dim MeterChallenge          ' Metering challenge from the client
    Dim MASPrivateKey           ' Private key
    Dim MeterCert               ' Metering certificate
    Dim MeterID                 ' Metering ID
    Dim TransID                 ' Transaction ID
    Dim ContentCollLength       ' Number of items in the 
                                ' content collection
    Dim ContentKeyID            ' Key ID for a content item
    Dim ActionCollLength        ' Number of items in the action collection
    Dim ActionName              ' Action name
    Dim ActionValue             ' Action count
    Dim MeterResponseString     ' Metering response string
    Dim x, y, a, b              ' Counters
    Dim ActionPlay              ' Metered play count
    Dim ActionCopy              ' Metered copy count.

    '"""""""""""""""""""""""""""""""""""""""""""""""""""""
    ' Set variables.
    '"""""""""""""""""""""""""""""""""""""""""""""""""""""
    ' This example assumes that the metering challenge was transmitted
    ' using the name "mchall".
    MeterChallenge = Request.form("mchall")
    MeterCert = "<Replace this with the metering certificate>"
    MASPrivateKey = "<Replace this with the private key.>"

    '"""""""""""""""""""""""""""""""""""""""""""""""""""""
    ' Extract metering data as a WMRMMeteringData object and as a string.
    '"""""""""""""""""""""""""""""""""""""""""""""""""""""
    Set MObj = Server.CreateObject("WMRMObjs.WMRMMetering")
    MObj.ServerPrivateKey = MASPrivateKey
    MObj.Challenge = MeterChallenge
    Set MDataObj = MObj.GetMeteringData
    MeterID = MDataObj.MeteringId
    TransID = MDataObj.TransactionId

    '"""""""""""""""""""""""""""""""""""""""""""""""""""""
    ' Retrieve the collection of content items.
    '"""""""""""""""""""""""""""""""""""""""""""""""""""""
    Set MContentCollObj = MDataObj.ContentCollection
    ContentCollLength = MContentCollObj.length

    '"""""""""""""""""""""""""""""""""""""""""""""""""""""
    ' Retrieve the key ID and action data for each content item.
    '"""""""""""""""""""""""""""""""""""""""""""""""""""""
    For x = 0 To (ContentCollLength - 1)
        ActionPlay = 0
        ActionCopy = 0

        Set MContentObj = MContentCollObj.item(x)
        ContentKeyID = MContentObj.KeyID

        ' Retrieve the collection of actions for the current content item.
        Set MActionCollObj = MContentObj.Actions
        ActionCollLength = MActionCollObj.length

        ' Retrieve each action and its value.
        For y = 0 To ActionCollLength - 1
            Set MActionObj = MActionCollObj.item(y)
            ActionName = MActionObj.Name
            ActionValue = MActionObj.Value
            If (ActionName = "Play") then ActionPlay = ActionValue
            If (ActionName = "Copy") then ActionCopy = ActionValue
        Next
        
        ' AggregateData() is a custom function not implemented
        ' in this example. In practice, this function would 
        ' increment play count and copy count values in a database.
        ' Including the transaction ID ensures that actions
        ' are not counted multiple times. 
        ' You should adjust counts to use the most
        ' recent totals for a given transaction ID.
        AggregateData(ContentKeyID, TransID, ActionPlay, ActionCopy)
    Next

    '"""""""""""""""""""""""""""""""""""""""""""""""""""""
    ' Generate the metering response.
    '"""""""""""""""""""""""""""""""""""""""""""""""""""""
    MObj.MeteringCertificate = MeterCert
    MeterResponseString = MObj.GetMeteringResponse
    response.write MeterResponseString

Loop while false

%>

Usando o Plug-in para Medições

Como os outros processos descritos neste artigo, as funções de medições no cliente devem ser realizadas em um processo separado. Este exemplo cria um processo para operações de medições quando o Windows Media Player 10 chama IWMPSubscriptionService2::deviceAvailable. Se uma operação de medição estiver em progresso, o código de exemplo simplesmente rejeita qualquer chamada subseqüente para realizar medições, o que significa que apenas uma operação de medição por vez é permitida. Pode-se decidir usar um pool de processos de forma a suportar operações de medições concorrentes.

Como IWMPSubscription2::prepareForSync, a chamada de deviceAvailable provê um ponteiro IWMPSubscriptionServicCallback. Neste cenário, seu código irá mais uma vez postar uma mensagem para a janela oculta do plug-in para completar o callback.

Para usar os objetos e interfaces do Windows Media Device Manager 10 SDK neste exemplo, deve-se incluir os seguintes arquivos:

// WMDM 10 includes.
#include <wmdrmdeviceapp.h>
#include <Scclient.h>
#include <mswmdm_i.c>
#include "wmdrmdeviceapp_i.c"

Os arquivos Wmdrmdeviceapp.h, Scclient.h e Mswmdm_i.c são instalados com o Windows Media Format 9.5 SDK na pasta \WMDM\Inc. O arquivo Wmdrmdeviceapp_i.c define o CLSID, LIBID e IID’s para o objeto WMDRMDeviceApp. Gera-se esse arquivo incluindo WMDRMDeviceApp.idl (que importa WMDM.idl) no seu projeto e compilando-o usando o compilador MIDL. Deve-se também adicionar uma referência para o arquivo Mssachlp.lib, que pode ser encontrado na pasta \WMDM\Lib.

Variáveis Membros

O código de exemplo a seguir declara uma variável membro que será necessária neste cenário. Essa variável deve ser declarada como um membro de sua classe plug-in, lembrando de inicializá-la com NULL no seu construtor de classe.

// Thread handle for the
// metering worker thread.
HANDLE  m_hMeteringThread

Gerenciando a Worker Thread (Processo de Trabalho)

Este exemplo usa a função específica a seguir:

STDMETHODIMP doMetering(BSTR bstrDeviceName,
                        IWMPSubscriptionServiceCallback *pCB, 
                        HWND hWndCB);

A função doMetering realiza a tarefa de criar a worker thread para iniciar o processo de medições. Chama-se doMetering de sua implementação de deviceAvailable. O parâmetro bstrDeviceName é o nome canônico do dispositivo informado pelo Windows Media Player 10. O parâmetro pCB é o ponteiro de callback informado pelo Windows Media Player 10. O parâmetro hWndCB é o tratador da janela oculta do plug-in.

O código de medições reusará a estrutura ThreadParams definida em uma seção anterior para transferir informações para a worker thread. Neste cenário, o membro bstrName corresponde ao nome do dispositivo. A implementação de exemplo de doMetering cria uma nova instância da estrutura ThreadParams, preenche seus membros e informa um ponteiro para a estrutura como um argumento de CreateThread. A worker thread libera essa memória quando o processamento for completado.

O código de exemplo a seguir mostra uma implementação de doMetering.

HRESULT CProsewarePlugin::doMetering(BSTR bstrDeviceName,
                      IWMPSubscriptionServiceCallback *pCB, 
                      HWND hWndCB)
{
    HRESULT hr = S_OK;
    CComBSTR bstrDevName;
    hr = bstrDevName.Append(bstrDeviceName);
    void *pvCB = NULL;

    if(SUCCEEDED(hr) && m_hMeteringThread)
    {
        DWORD dwExitCode = 0;
        // Test whether the thread handle represents
        // an active metering thread. If not, close 
        // it so we can create a new thread.
        if(GetExitCodeThread(m_hMeteringThread, &dwExitCode))
        {
            if(STILL_ACTIVE != dwExitCode)
            {
                CloseHandle(m_hMeteringThread);
                m_hMeteringThread = NULL;
            }
        }
    }
  
    if(SUCCEEDED(hr) && 
       NULL == m_hMeteringThread)
    {
        ThreadParams *pTP = new ThreadParams;
        if(NULL == pTP)
        {
            hr = E_OUTOFMEMORY;
        }

        if(SUCCEEDED(hr))
        {
            if(pCB)
            {   
                pCB->AddRef();
                pvCB = reinterpret_cast<void*>(pCB);
            }

            // Fill in the struct.
            ZeroMemory(pTP, sizeof(ThreadParams));
            bstrDevName.CopyTo(&pTP->bstrName);
            pTP->pCB = pvCB;
            pTP->hWndCB = hWndCB;
      
            m_hMeteringThread = CreateThread(NULL, 0, 
                       MeteringThread, (LPVOID)pTP, NULL, 0);
        }
    }
    else
    {
        hr = E_ACCESSDENIED;
    }

    return hr;
}

O procedimento realizado pela worker thread segue os seguintes passos básicos:

  1. Co-criar o objeto WMDRMDeviceApp;

  2. Co-criar o objeto MediaDevMgr;

  3. Recuperar um ponteiro para a interface IWMDeviceManager2;

  4. Recuperar o nome canônico do dispositivo;

  5. Verificar o status do dispositivo;

  6. Gerar um convite de medição;

  7. Transmitir o convite para o serviço de agregação de medição e receber a resposta;

  8. Processar a resposta de medição.

O código de exemplo a seguir mostra uma implementação do procedimento.

// Variable to contain metering certificate.
const WCHAR *METERCERT = L"<Replace this with the metering certificate.>";

DWORD WINAPI MeteringThread(void *pArg)
{
    if(!pArg)
    {
        return 1;
    }
  
    CoInitialize(NULL);
    HRESULT hr = S_OK;

    // Get the members from the param struct.
    // It is valid for all of these to be NULL
    // except pThis.
    ThreadParams *pTP = reinterpret_cast<ThreadParams*>(pArg);    
    void *pCB = pTP->pCB;
    CComBSTR bstrDeviceName;
    bstrDeviceName.Attach(pTP->bstrName);
    HWND hwndCB = pTP->hWndCB;

    CComPtr<IWMDRMDeviceApp> spDeviceApp;
    CComPtr<IWMDeviceManager> spDevMgr;
    CComPtr<IWMDeviceManager2> spDevMgr2;
    CComPtr<IWMDMDevice> spDevice;
    CComBSTR bstrMeterCert;
    hr = bstrMeterCert.Append(METERCERT);
    CComBSTR bstrMeterURL;
    CComBSTR bstrMeteringData;
    CComBSTR bstrResponse;
    DWORD dwProcessFlags = 0;
    DWORD dwBufferSize = 0;
 
    if(SUCCEEDED(hr))
    {
        // Create the WMDRMDeviceApp object.
        hr = spDeviceApp.CoCreateInstance(CLSID_WMDRMDeviceApp, 0,
                CLSCTX_ALL); 
    }

    if(SUCCEEDED(hr))
    {
        // Create the Device Manager object.
        hr = spDevMgr.CoCreateInstance(CLSID_MediaDevMgr, 0, CLSCTX_ALL);
    }

    // Test whether called with a device name.
    if(bstrDeviceName.Length() > 0)
    {
        // QueryInterface for IWMDeviceManager2.
        hr = spDevMgr.QueryInterface(&spDevMgr2);
        if(SUCCEEDED(hr))
        {   
            // Retrieve a pointer to the device.
            hr = spDevMgr2->GetDeviceFromCanonicalName(bstrDeviceName,
                                                       &spDevice);
        }

        if(SUCCEEDED(hr) && spDevice)
        {
            // Verify that the device supports metering.
            CComPtr<IWMDMDevice2> spDevice2;
            DWORD dwFlags = 0;

            hr = spDevice->QueryInterface(&spDevice2);
            if(SUCCEEDED(hr))
            {
                hr = spDeviceApp->QueryDeviceStatus(spDevice2, &dwFlags);
            }

            if(SUCCEEDED(hr))
            {
                if(!(dwFlags & WMDRM_DEVICE_ISWMDRM))
                {
                    hr = NS_E_DEVICE_NOT_WMDRM_DEVICE;
                }
            }
        }
    }
   
    if(SUCCEEDED(hr) &&
        spDeviceApp)
    {
        // NULL device pointer means to meter the PC license store.
        hr =  spDeviceApp->GenerateMeterChallenge(spDevice,
                                                  bstrMeterCert,
                                                  &bstrMeterURL,
                                                  &bstrMeteringData);
    }
   
    if(SUCCEEDED(hr) &&
       bstrMeteringData.Length())
    {
        // Call the function that sends the challenge to the ASP
        // page using HTTP and receives the metering response.
        // This is a placeholder function for this example.
        hr = doChallengeResponse(bstrMeterURL,
                                 bstrMeteringData,
                                 &bstrResponse);
    } 

    if(SUCCEEDED(hr) &&
       bstrResponse.Length())
    {
        dwBufferSize = bstrResponse.ByteLength();
        BYTE *pbyte = new BYTE[dwBufferSize];
        if(NULL == pbyte)
        {
            hr = E_OUTOFMEMORY;
        }

        if(SUCCEEDED(hr))
        {
            // Process the metering response to reset the
            // the metering store. 
            memcpy(pbyte, bstrResponse.m_str, dwBufferSize);
            hr = spDeviceApp->ProcessMeterResponse(spDevice, pbyte, 
                                                dwBufferSize, 
                                                &dwProcessFlags);
        }

        if(pbyte)
        {
            delete[] pbyte;
            pbyte = NULL;
        }
    }

    if(hwndCB && pCB)
    {
        UINT uMarshalCustom = RegisterWindowMessage(
                        _T("MSSampleMusicPlugin_CB"));

        if(uMarshalCustom)
        {
            // Post a message to the plug-in window to make the callback 
            // to Windows Media Player.
            PostMessage(hwndCB, uMarshalCustom, (WPARAM)hr, (LPARAM)pCB);
        }
    }

    if(pTP)
    {
        delete pTP;
        pTP = NULL;
    }

    ATLASSERT(hr);

    CoUninitialize();

    return SUCCEEDED(hr)? 0:1;
}

No código anterior, doChallengeResponse é uma função específica que transmite o convite de medição para o serviço de agregação de medições e recupera a string de resposta. Este artigo não provê um exemplo de implementação para esta função.

Note que o valor retornado no quarto parâmetro da chamada de ProcessMeterResponse (dwProcessFlags) pode indicar que a resposta de medição é parcial. Este exemplo ignora esse valor, processando apenas os dados retornados na chamada principal. Pode-se usar essa informação para requisitar e processar dados repetidamente.

ára Deve-se observar que o processo posta uma mensagem Windows para a janela oculta do plug-in para completar o callback do Windows Media Player 10. Esse é o mesmo mecanismo utilizado na seção anterior sobre preparação para sincronização. Nesse caso, o callback sinaliza o Windows Media Player 10 que foi completado o trabalho com o dispositivo portátil. O código HRESULT retornado não é transportado para o usuário. Deve-se sempre retornar S_OK nesta instância.

Finalizando o Processo

Deve-se agora modificar o código na implementação de FinalRelease de sua classe plug-in para garantir que a worker thread finalize quando o plug-in é desligado. O código de exemplo a seguir modifica o código de exemplo de saída do processo mostrado na seção anterior. É adicionado o tratador (handler) da worker thread de medição ao array de tratadores passado a WaitForMultipleObjects.

HANDLE hThreads[3] = {m_hLicenseUpdateThread,
                      m_hPrepareForSyncThread,
                      m_hMeteringThread};
// Wait for the threads to exit.
WaitForMultipleObjects(3, hThreads, TRUE, 15000);

Para Mais Informações